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, *hash, ifname[IFNAMSIZ], ipaddr[INET6_ADDRSTRLEN] = { 0 }; 630 unsigned int port = default_port; 631 unsigned int scope = 0; 632 633 hash = strchr(addrstr, '#'); 634 635 if (hash) { 636 port = strtoul(hash + 1, &eptr, 10); 637 638 if ((size_t)(hash - addrstr) >= sizeof(ipaddr) || 639 eptr == hash + 1 || *eptr != '\0' || port > 65535) { 640 errno = EINVAL; 641 return -1; 642 } 643 644 memcpy(ipaddr, addrstr, hash - addrstr); 645 } 646 else { 647 strncpy(ipaddr, addrstr, sizeof(ipaddr) - 1); 648 } 649 650 hash = strchr(addrstr, '%'); 651 652 if (hash) { 653 for (eptr = ++hash; *eptr != '\0' && *eptr != '#'; eptr++) { 654 if ((eptr - hash) >= IFNAMSIZ) { 655 errno = ENODEV; 656 return -1; 657 } 658 659 ifname[eptr - hash] = *eptr; 660 } 661 662 ifname[eptr - hash] = '\0'; 663 scope = if_nametoindex(ifname); 664 665 if (scope == 0) { 666 errno = ENODEV; 667 return -1; 668 } 669 } 670 671 if (inet_pton(AF_INET6, ipaddr, &lsa->u.sin6.sin6_addr)) { 672 lsa->u.sin6.sin6_family = AF_INET6; 673 lsa->u.sin6.sin6_port = htons(port); 674 lsa->u.sin6.sin6_scope_id = scope; 675 lsa->len = sizeof(lsa->u.sin6); 676 return 0; 677 } 678 679 if (!scope && inet_pton(AF_INET, ipaddr, &lsa->u.sin.sin_addr)) { 680 lsa->u.sin.sin_family = AF_INET; 681 lsa->u.sin.sin_port = htons(port); 682 lsa->len = sizeof(lsa->u.sin); 683 return 0; 684 } 685 686 errno = EINVAL; 687 return -1; 688 } 689 690 static char * 691 make_ptr(const char *addrstr) 692 { 693 const char *hexdigit = "0123456789abcdef"; 694 static char ptrstr[73]; 695 unsigned char addr[16]; 696 char *ptr = ptrstr; 697 int i; 698 699 if (inet_pton(AF_INET6, addrstr, addr)) { 700 if (memcmp(addr, "\0\0\0\0\0\0\0\0\0\0\xff\xff", 12) != 0) { 701 for (i = 0; i < 16; i++) { 702 *ptr++ = hexdigit[(unsigned char)addr[15 - i] & 0xf]; 703 *ptr++ = '.'; 704 *ptr++ = hexdigit[(unsigned char)addr[15 - i] >> 4]; 705 *ptr++ = '.'; 706 } 707 strcpy(ptr, "ip6.arpa"); 708 } 709 else { 710 sprintf(ptr, "%u.%u.%u.%u.in-addr.arpa", 711 addr[15], addr[14], addr[13], addr[12]); 712 } 713 714 return ptrstr; 715 } 716 717 if (inet_pton(AF_INET, addrstr, addr)) { 718 sprintf(ptr, "%u.%u.%u.%u.in-addr.arpa", 719 addr[3], addr[2], addr[1], addr[0]); 720 return ptrstr; 721 } 722 723 return NULL; 724 } 725 726 static unsigned long 727 mtime(void) 728 { 729 struct timespec ts; 730 clock_gettime(CLOCK_REALTIME, &ts); 731 732 return (unsigned long)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; 733 } 734 735 static void 736 to_v4_mapped(addr_t *a) 737 { 738 if (a->u.sa.sa_family != AF_INET) 739 return; 740 741 memcpy(a->u.sin6.sin6_addr.s6_addr + 12, 742 &a->u.sin.sin_addr, 4); 743 744 memcpy(a->u.sin6.sin6_addr.s6_addr, 745 "\0\0\0\0\0\0\0\0\0\0\xff\xff", 12); 746 747 a->u.sin6.sin6_family = AF_INET6; 748 a->u.sin6.sin6_flowinfo = 0; 749 a->u.sin6.sin6_scope_id = 0; 750 a->len = sizeof(a->u.sin6); 751 } 752 753 static void 754 add_status(uc_vm_t *vm, uc_value_t *res_obj, const char *name, const char *rcode) 755 { 756 uc_value_t *name_obj = init_obj(vm, res_obj, name, UC_OBJECT); 757 758 ucv_object_add(name_obj, "rcode", ucv_string_new(rcode)); 759 } 760 761 /* 762 * Function logic borrowed & modified from musl libc, res_msend.c 763 */ 764 765 static int 766 send_queries(resolve_ctx_t *ctx, uc_vm_t *vm, uc_value_t *res_obj) 767 { 768 int fd, flags; 769 int servfail_retry = 0; 770 addr_t from = { }; 771 int one = 1; 772 int recvlen = 0; 773 int n_replies = 0; 774 struct pollfd pfd; 775 unsigned long t0, t1, t2, timeout = ctx->timeout, retry_interval; 776 unsigned int nn, qn, next_query = 0; 777 struct { unsigned char *buf; size_t len; } reply_buf = { 0 }; 778 779 from.u.sa.sa_family = AF_INET; 780 from.len = sizeof(from.u.sin); 781 782 for (nn = 0; nn < ctx->n_ns; nn++) { 783 if (ctx->ns[nn].addr.u.sa.sa_family == AF_INET6) { 784 from.u.sa.sa_family = AF_INET6; 785 from.len = sizeof(from.u.sin6); 786 break; 787 } 788 } 789 790 #ifdef __APPLE__ 791 flags = SOCK_DGRAM; 792 #else 793 flags = SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK; 794 #endif 795 796 /* Get local address and open/bind a socket */ 797 fd = socket(from.u.sa.sa_family, flags, 0); 798 799 /* Handle case where system lacks IPv6 support */ 800 if (fd < 0 && from.u.sa.sa_family == AF_INET6 && errno == EAFNOSUPPORT) { 801 fd = socket(AF_INET, flags, 0); 802 from.u.sa.sa_family = AF_INET; 803 } 804 805 if (fd < 0) { 806 set_error(errno, "Unable to open UDP socket"); 807 808 return -1; 809 } 810 811 #ifdef __APPLE__ 812 flags = fcntl(fd, F_GETFD); 813 814 if (flags < 0) { 815 set_error(errno, "Unable to acquire socket descriptor flags"); 816 close(fd); 817 818 return -1; 819 } 820 821 if (fcntl(fd, F_SETFD, flags|O_CLOEXEC|O_NONBLOCK) < 0) { 822 set_error(errno, "Unable to set socket descriptor flags"); 823 close(fd); 824 825 return -1; 826 } 827 #endif 828 829 if (bind(fd, &from.u.sa, from.len) < 0) { 830 set_error(errno, "Unable to bind UDP socket"); 831 close(fd); 832 833 return -1; 834 } 835 836 /* Convert any IPv4 addresses in a mixed environment to v4-mapped */ 837 if (from.u.sa.sa_family == AF_INET6) { 838 setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)); 839 840 for (nn = 0; nn < ctx->n_ns; nn++) 841 to_v4_mapped(&ctx->ns[nn].addr); 842 } 843 844 pfd.fd = fd; 845 pfd.events = POLLIN; 846 retry_interval = timeout / ctx->retries; 847 t0 = t2 = mtime(); 848 t1 = t2 - retry_interval; 849 850 for (; t2 - t0 < timeout; t2 = mtime()) { 851 if (t2 - t1 >= retry_interval) { 852 for (qn = 0; qn < ctx->n_queries; qn++) { 853 if (ctx->queries[qn].rcode == 0 || ctx->queries[qn].rcode == 3) 854 continue; 855 856 for (nn = 0; nn < ctx->n_ns; nn++) { 857 sendto(fd, ctx->queries[qn].query, ctx->queries[qn].qlen, 858 MSG_NOSIGNAL, &ctx->ns[nn].addr.u.sa, ctx->ns[nn].addr.len); 859 } 860 } 861 862 t1 = t2; 863 servfail_retry = 2 * ctx->n_queries; 864 } 865 866 /* Wait for a response, or until time to retry */ 867 switch (poll(&pfd, 1, t1+retry_interval-t2)) { 868 case 0: 869 /* timeout */ 870 for (qn = 0; qn < ctx->n_queries; qn++) { 871 if (ctx->queries[qn].rcode != -1) 872 continue; 873 874 for (nn = 0; nn < ctx->n_ns; nn++) 875 add_status(vm, res_obj, ctx->queries[qn].name, "TIMEOUT"); 876 } 877 878 continue; 879 880 case -1: 881 /* error */ 882 continue; 883 } 884 885 while (1) { 886 recvlen = recvfrom(fd, NULL, 0, MSG_PEEK|MSG_TRUNC, &from.u.sa, &from.len); 887 888 /* read error */ 889 if (recvlen < 0) 890 break; 891 892 if ((size_t)recvlen > reply_buf.len) { 893 reply_buf.buf = xrealloc(reply_buf.buf, recvlen); 894 reply_buf.len = recvlen; 895 } 896 897 recvlen = recvfrom(fd, reply_buf.buf, recvlen, 0, &from.u.sa, &from.len); 898 899 /* Ignore non-identifiable packets */ 900 if (recvlen < 4) 901 continue; 902 903 /* Ignore replies from addresses we didn't send to */ 904 for (nn = 0; nn < ctx->n_ns; nn++) 905 if (memcmp(&from.u.sa, &ctx->ns[nn].addr.u.sa, from.len) == 0) 906 break; 907 908 if (nn >= ctx->n_ns) 909 continue; 910 911 /* Find which query this answer goes with, if any */ 912 for (qn = next_query; qn < ctx->n_queries; qn++) 913 if (!memcmp(reply_buf.buf, ctx->queries[qn].query, 2)) 914 break; 915 916 /* Do not overwrite previous replies from other servers 917 * but allow overwriting preexisting NXDOMAIN reply */ 918 if (qn >= ctx->n_queries || 919 ctx->queries[qn].rcode == 0 || 920 (ctx->queries[qn].rcode == 3 && (reply_buf.buf[3] & 15) != 0)) 921 continue; 922 923 ctx->queries[qn].rcode = reply_buf.buf[3] & 15; 924 925 switch (ctx->queries[qn].rcode) { 926 case 0: 927 ucv_object_delete( 928 ucv_object_get(res_obj, ctx->queries[qn].name, NULL), 929 "rcodes"); 930 931 break; 932 933 case 2: 934 /* Retry immediately on server failure. */ 935 if (servfail_retry && servfail_retry--) 936 sendto(fd, ctx->queries[qn].query, ctx->queries[qn].qlen, 937 MSG_NOSIGNAL, &ctx->ns[nn].addr.u.sa, ctx->ns[nn].addr.len); 938 939 /* fall through */ 940 941 default: 942 add_status(vm, res_obj, ctx->queries[qn].name, 943 rcodes[ctx->queries[qn].rcode]); 944 } 945 946 /* Store answer */ 947 n_replies++; 948 949 ctx->queries[qn].rlen = recvlen; 950 951 parse_reply(vm, res_obj, reply_buf.buf, recvlen, ctx->txt_as_array); 952 953 if (qn == next_query) { 954 while (next_query < ctx->n_queries) { 955 if (ctx->queries[next_query].rcode == -1) 956 break; 957 958 next_query++; 959 } 960 } 961 962 if (next_query >= ctx->n_queries) 963 goto out; 964 } 965 } 966 967 out: 968 free(reply_buf.buf); 969 close(fd); 970 971 return n_replies; 972 } 973 974 static ns_t * 975 add_ns(resolve_ctx_t *ctx, const char *addr) 976 { 977 char portstr[sizeof("65535")], *p; 978 addr_t a = { }; 979 struct addrinfo *ai, *aip, hints = { 980 .ai_flags = AI_NUMERICSERV, 981 .ai_socktype = SOCK_DGRAM 982 }; 983 984 if (parse_nsaddr(addr, &a)) { 985 /* Maybe we got a domain name, attempt to resolve it using the standard 986 * resolver routines */ 987 988 p = strchr(addr, '#'); 989 snprintf(portstr, sizeof(portstr), "%hu", 990 (unsigned short)(p ? strtoul(p, NULL, 10) : default_port)); 991 992 if (!getaddrinfo(addr, portstr, &hints, &ai)) { 993 for (aip = ai; aip; aip = aip->ai_next) { 994 if (aip->ai_addr->sa_family != AF_INET && 995 aip->ai_addr->sa_family != AF_INET6) 996 continue; 997 998 ctx->ns = xrealloc(ctx->ns, sizeof(*ctx->ns) * (ctx->n_ns + 1)); 999 ctx->ns[ctx->n_ns].name = addr; 1000 ctx->ns[ctx->n_ns].addr.len = aip->ai_addrlen; 1001 1002 memcpy(&ctx->ns[ctx->n_ns].addr.u.sa, aip->ai_addr, aip->ai_addrlen); 1003 1004 ctx->n_ns++; 1005 } 1006 1007 freeaddrinfo(ai); 1008 1009 return &ctx->ns[ctx->n_ns]; 1010 } 1011 1012 return NULL; 1013 } 1014 1015 ctx->ns = xrealloc(ctx->ns, sizeof(*ctx->ns) * (ctx->n_ns + 1)); 1016 ctx->ns[ctx->n_ns].addr = a; 1017 ctx->ns[ctx->n_ns].name = addr; 1018 1019 return &ctx->ns[ctx->n_ns++]; 1020 } 1021 1022 static int 1023 parse_resolvconf(resolve_ctx_t *ctx) 1024 { 1025 int prev_n_ns = ctx->n_ns; 1026 char line[128], *p; 1027 FILE *resolv; 1028 bool ok; 1029 1030 if ((resolv = fopen("/etc/resolv.conf", "r")) != NULL) { 1031 while (fgets(line, sizeof(line), resolv)) { 1032 p = strtok(line, " \t\n"); 1033 1034 if (!p || strcmp(p, "nameserver")) 1035 continue; 1036 1037 p = strtok(NULL, " \t\n"); 1038 1039 if (!p) 1040 continue; 1041 1042 p = xstrdup(p); 1043 ok = add_ns(ctx, p); 1044 1045 free(p); 1046 1047 if (!ok) 1048 break; 1049 } 1050 1051 fclose(resolv); 1052 } 1053 1054 return ctx->n_ns - prev_n_ns; 1055 } 1056 1057 static query_t * 1058 add_query(resolve_ctx_t *ctx, int type, const char *dname) 1059 { 1060 opt_rr_t *opt; 1061 ssize_t qlen; 1062 1063 ctx->queries = xrealloc(ctx->queries, sizeof(*ctx->queries) * (ctx->n_queries + 1)); 1064 1065 memset(&ctx->queries[ctx->n_queries], 0, sizeof(*ctx->queries)); 1066 1067 qlen = res_mkquery(QUERY, dname, C_IN, type, NULL, 0, NULL, 1068 ctx->queries[ctx->n_queries].query, 1069 sizeof(ctx->queries[ctx->n_queries].query)); 1070 1071 /* add OPT record */ 1072 if (ctx->edns_maxsize != 0 && qlen + sizeof(opt_rr_t) <= sizeof(ctx->queries[ctx->n_queries].query)) { 1073 ctx->queries[ctx->n_queries].query[11] = 1; 1074 1075 opt = (opt_rr_t *)&ctx->queries[ctx->n_queries].query[qlen]; 1076 opt->root_domain = 0; 1077 opt->type = htons(41); 1078 opt->edns_maxsize = htons(ctx->edns_maxsize); 1079 opt->extended_rcode = 0; 1080 opt->edns_version = 0; 1081 opt->z = htons(0); 1082 opt->data_length = htons(0); 1083 1084 qlen += sizeof(opt_rr_t); 1085 } 1086 1087 ctx->queries[ctx->n_queries].qlen = qlen; 1088 ctx->queries[ctx->n_queries].name = xstrdup(dname); 1089 ctx->queries[ctx->n_queries].rcode = -1; 1090 1091 return &ctx->queries[ctx->n_queries++]; 1092 } 1093 1094 static bool 1095 check_types(uc_value_t *typenames, uint32_t *types) 1096 { 1097 size_t i; 1098 1099 *types = 0; 1100 1101 for_each_item(typenames, typename) { 1102 if (ucv_type(typename) != UC_STRING) 1103 err_return(EINVAL, "Query type value not a string"); 1104 1105 for (i = 0; qtypes[i].name; i++) { 1106 if (!strcasecmp(ucv_string_get(typename), qtypes[i].name)) { 1107 *types |= (1 << i); 1108 break; 1109 } 1110 } 1111 1112 if (!qtypes[i].name) 1113 err_return(EINVAL, "Unrecognized query type '%s'", 1114 ucv_string_get(typename)); 1115 } 1116 1117 return true; 1118 } 1119 1120 static void 1121 add_queries(resolve_ctx_t *ctx, uc_value_t *name) 1122 { 1123 char *s = ucv_string_get(name); 1124 char *ptr; 1125 size_t i; 1126 1127 if (ctx->qtypes == 0) { 1128 ptr = make_ptr(s); 1129 1130 if (ptr) { 1131 add_query(ctx, ns_t_ptr, ptr); 1132 } 1133 else { 1134 add_query(ctx, ns_t_a, s); 1135 add_query(ctx, ns_t_aaaa, s); 1136 } 1137 } 1138 else { 1139 for (i = 0; qtypes[i].name; i++) { 1140 if (ctx->qtypes & (1 << i)) { 1141 if (qtypes[i].type == ns_t_ptr) { 1142 ptr = make_ptr(s); 1143 add_query(ctx, ns_t_ptr, ptr ? ptr : s); 1144 } 1145 else { 1146 add_query(ctx, qtypes[i].type, s); 1147 } 1148 } 1149 } 1150 } 1151 } 1152 1153 static bool 1154 parse_options(resolve_ctx_t *ctx, uc_value_t *opts) 1155 { 1156 uc_value_t *v; 1157 1158 if (!check_types(ucv_object_get(opts, "type", NULL), &ctx->qtypes)) 1159 return false; 1160 1161 for_each_item(ucv_object_get(opts, "nameserver", NULL), server) { 1162 if (ucv_type(server) != UC_STRING) 1163 err_return(EINVAL, "Nameserver value not a string"); 1164 1165 if (!add_ns(ctx, ucv_string_get(server))) 1166 err_return(EINVAL, "Unable to resolve nameserver address '%s'", 1167 ucv_string_get(server)); 1168 } 1169 1170 /* Find NS servers in resolv.conf if none provided */ 1171 if (ctx->n_ns == 0) 1172 parse_resolvconf(ctx); 1173 1174 /* Fall back to localhost if we could not find NS in resolv.conf */ 1175 if (ctx->n_ns == 0) 1176 add_ns(ctx, "127.0.0.1"); 1177 1178 v = ucv_object_get(opts, "retries", NULL); 1179 1180 if (ucv_type(v) == UC_INTEGER) 1181 ctx->retries = ucv_uint64_get(v); 1182 else if (v) 1183 err_return(EINVAL, "Retries value not an integer"); 1184 1185 v = ucv_object_get(opts, "timeout", NULL); 1186 1187 if (ucv_type(v) == UC_INTEGER) 1188 ctx->timeout = ucv_uint64_get(v); 1189 else if (v) 1190 err_return(EINVAL, "Timeout value not an integer"); 1191 1192 v = ucv_object_get(opts, "edns_maxsize", NULL); 1193 1194 if (ucv_type(v) == UC_INTEGER) 1195 ctx->edns_maxsize = ucv_uint64_get(v); 1196 else if (v) 1197 err_return(EINVAL, "EDNS max size not an integer"); 1198 1199 v = ucv_object_get(opts, "txt_as_array", NULL); 1200 1201 if (ucv_type(v) == UC_BOOLEAN) 1202 ctx->txt_as_array = ucv_boolean_get(v); 1203 else if (v) 1204 err_return(EINVAL, "Array TXT record flag not a boolean"); 1205 1206 return true; 1207 } 1208 1209 /** 1210 * Perform DNS queries for specified domain names. 1211 * 1212 * The `query()` function performs DNS lookups for one or more domain names 1213 * according to the specified options. It returns a structured object containing 1214 * all resolved DNS records grouped by domain name and record type. 1215 * 1216 * If no record types are specified in the options, the function will perform 1217 * both A and AAAA record lookups for regular domain names, or PTR record 1218 * lookups for IP addresses (reverse DNS). 1219 * 1220 * Returns an object containing DNS query results organized by domain name. 1221 * 1222 * Raises a runtime exception if invalid arguments are provided or if DNS 1223 * resolution encounters critical errors. 1224 * 1225 * @function module:resolv#query 1226 * 1227 * @param {string|string[]} names 1228 * Domain name(s) to query. Can be a single domain name string or an array 1229 * of domain name strings. IP addresses can also be provided for reverse 1230 * DNS lookups. 1231 * 1232 * @param {object} [options] 1233 * Query options object. 1234 * 1235 * @param {string[]} [options.type] 1236 * Array of DNS record types to query for. Valid types are: 'A', 'AAAA', 1237 * 'CNAME', 'MX', 'NS', 'PTR', 'SOA', 'SRV', 'TXT', 'ANY'. If not specified, 1238 * defaults to 'A' and 'AAAA' for domain names, or 'PTR' for IP addresses. 1239 * 1240 * @param {string[]} [options.nameserver] 1241 * Array of DNS nameserver addresses to query. Each address can optionally 1242 * include a port number using '#' separator (e.g., '8.8.8.8#53'). IPv6 1243 * addresses can include interface scope using '%' separator. If not specified, 1244 * nameservers are read from /etc/resolv.conf, falling back to '127.0.0.1'. 1245 * 1246 * @param {number} [options.timeout=5000] 1247 * Total timeout for all queries in milliseconds. 1248 * 1249 * @param {number} [options.retries=2] 1250 * Number of retry attempts for failed queries. 1251 * 1252 * @param {number} [options.edns_maxsize=4096] 1253 * Maximum UDP packet size for EDNS (Extension Mechanisms for DNS). Set to 0 1254 * to disable EDNS. 1255 * 1256 * @param {boolean} [options.txt_as_array=false] 1257 * Return TXT record strings as array elements instead of space-joining all 1258 * record strings into one single string per record. 1259 * 1260 * @returns {object} 1261 * Object containing DNS query results. Keys are domain names, values are 1262 * objects containing arrays of records grouped by type, or error information 1263 * for failed queries. 1264 * 1265 * @example 1266 * // Basic A and AAAA record lookup 1267 * const result = query('example.com'); 1268 * print(result, "\n"); 1269 * // { 1270 * // "example.com": { 1271 * // "A": ["192.0.2.1"], 1272 * // "AAAA": ["2001:db8::1"] 1273 * // } 1274 * // } 1275 * 1276 * @example 1277 * // Specific record type queries 1278 * const mxResult = query('example.com', { type: ['MX'] }); 1279 * print(mxResult, "\n"); 1280 * // { 1281 * // "example.com": { 1282 * // "MX": [[10, "mail.example.com"]] 1283 * // } 1284 * // } 1285 * 1286 * @example 1287 * // Multiple domains and types with custom nameserver 1288 * const results = query( 1289 * ['example.com', 'google.com'], 1290 * { 1291 * type: ['A', 'MX'], 1292 * nameserver: ['8.8.8.8', '1.1.1.1'], 1293 * timeout: 10000 1294 * } 1295 * ); 1296 * 1297 * @example 1298 * // Reverse DNS lookup 1299 * const ptrResult = query(['192.0.2.1'], { type: ['PTR'] }); 1300 * print(ptrResult, "\n"); 1301 * // { 1302 * // "1.2.0.192.in-addr.arpa": { 1303 * // "PTR": ["example.com"] 1304 * // } 1305 * // } 1306 * 1307 * @example 1308 * // TXT record with multiple elements 1309 * const txtResult = query(['_spf.facebook.com'], { type: ['TXT'], txt_as_array: true }); 1310 * printf(txtResult, "\n"); 1311 * // { 1312 * // "_spf.facebook.com": { 1313 * // "TXT": [ 1314 * // [ 1315 * // "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", 1316 * // " ip4:69.171.232.0/24 ip4:69.171.244.0/23 -all" 1317 * // ] 1318 * // ] 1319 * // } 1320 * // } 1321 * 1322 * @example 1323 * // Handling errors 1324 * const errorResult = query(['nonexistent.example.com']); 1325 * print(errorResult, "\n"); 1326 * // { 1327 * // "nonexistent.example.com": { 1328 * // "rcode": "NXDOMAIN" 1329 * // } 1330 * // } 1331 */ 1332 static uc_value_t * 1333 uc_resolv_query(uc_vm_t *vm, size_t nargs) 1334 { 1335 resolve_ctx_t ctx = { .retries = 2, .timeout = 5000, .edns_maxsize = 4096 }; 1336 uc_value_t *names = uc_fn_arg(0); 1337 uc_value_t *opts = uc_fn_arg(1); 1338 uc_value_t *res_obj = NULL; 1339 1340 if (!parse_options(&ctx, opts)) 1341 goto err; 1342 1343 for_each_item(names, name) { 1344 if (ucv_type(name) != UC_STRING) { 1345 set_error(EINVAL, "Domain name value not a string"); 1346 goto err; 1347 } 1348 1349 add_queries(&ctx, name); 1350 } 1351 1352 res_obj = ucv_object_new(vm); 1353 1354 if (send_queries(&ctx, vm, res_obj) == 0) 1355 set_error(ETIMEDOUT, "Server did not respond"); 1356 1357 err: 1358 while (ctx.n_queries) 1359 free(ctx.queries[--ctx.n_queries].name); 1360 1361 free(ctx.queries); 1362 free(ctx.ns); 1363 1364 return res_obj; 1365 } 1366 1367 /** 1368 * Get the last error message from DNS operations. 1369 * 1370 * The `error()` function returns a descriptive error message for the last 1371 * failed DNS operation, or `null` if no error occurred. This function is 1372 * particularly useful for debugging DNS resolution issues. 1373 * 1374 * After calling this function, the stored error state is cleared, so 1375 * subsequent calls will return `null` unless a new error occurs. 1376 * 1377 * Returns a string describing the last error, or `null` if no error occurred. 1378 * 1379 * @function module:resolv#error 1380 * 1381 * @returns {string|null} 1382 * A descriptive error message for the last failed operation, or `null` if 1383 * no error occurred. 1384 * 1385 * @example 1386 * // Check for errors after a failed query 1387 * const result = query("example.org", { nameserver: "invalid..domain" }); 1388 * const err = error(); 1389 * if (err) { 1390 * print("DNS query failed: ", err, "\n"); 1391 * } 1392 */ 1393 static uc_value_t * 1394 uc_resolv_error(uc_vm_t *vm, size_t nargs) 1395 { 1396 uc_stringbuf_t *buf; 1397 const char *s; 1398 1399 if (last_error.code == 0) 1400 return NULL; 1401 1402 buf = ucv_stringbuf_new(); 1403 1404 s = strerror(last_error.code); 1405 1406 ucv_stringbuf_addstr(buf, s, strlen(s)); 1407 1408 if (last_error.msg) 1409 ucv_stringbuf_printf(buf, ": %s", last_error.msg); 1410 1411 set_error(0, NULL); 1412 1413 return ucv_stringbuf_finish(buf); 1414 } 1415 1416 1417 static const uc_function_list_t resolv_fns[] = { 1418 { "query", uc_resolv_query }, 1419 { "error", uc_resolv_error }, 1420 }; 1421 1422 void uc_module_init(uc_vm_t *vm, uc_value_t *scope) 1423 { 1424 uc_function_list_register(scope, resolv_fns); 1425 } 1426
This page was automatically generated by LXR 0.3.1. • OpenWrt