• 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, *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