• source navigation  • diff markup  • identifier search  • freetext search  • 

Sources/ucode/lib/resolv.c

  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