1use super::arg_str;
17use crate::host::{with_host, JsObj};
18use fusevm::Value;
19use hickory_resolver::config::{NameServerConfigGroup, ResolverConfig, ResolverOpts};
20use hickory_resolver::error::{ResolveError, ResolveErrorKind};
21use hickory_resolver::proto::op::ResponseCode;
22use hickory_resolver::proto::rr::rdata::caa::{Property as CaaProperty, Value as CaaValue};
23use hickory_resolver::proto::rr::{Name, RData, RecordType};
24use hickory_resolver::Resolver;
25use indexmap::IndexMap;
26use std::cell::RefCell;
27use std::net::{IpAddr, ToSocketAddrs};
28
29pub const METHODS: &[&str] = &[
30 "lookup",
32 "lookupService",
33 "resolve",
35 "resolve4",
36 "resolve6",
37 "resolveMx",
38 "resolveTxt",
39 "resolveCname",
40 "resolveNs",
41 "resolvePtr",
42 "resolveSrv",
43 "resolveSoa",
44 "resolveNaptr",
45 "resolveCaa",
46 "resolveTlsa",
47 "resolveAny",
48 "reverse",
49 "getServers",
51 "setServers",
52 "getDefaultResultOrder",
53 "setDefaultResultOrder",
54 "promiseLookup",
56 "promiseLookupService",
57 "promiseResolve",
58 "promiseResolve4",
59 "promiseResolve6",
60 "promiseResolveMx",
61 "promiseResolveTxt",
62 "promiseResolveCname",
63 "promiseResolveNs",
64 "promiseResolvePtr",
65 "promiseResolveSrv",
66 "promiseResolveSoa",
67 "promiseResolveNaptr",
68 "promiseResolveCaa",
69 "promiseResolveTlsa",
70 "promiseResolveAny",
71 "promiseReverse",
72];
73
74pub const PROMISES_METHODS: &[&str] = &[
79 "lookup",
80 "lookupService",
81 "resolve",
82 "resolve4",
83 "resolve6",
84 "resolveMx",
85 "resolveTxt",
86 "resolveCname",
87 "resolveNs",
88 "resolvePtr",
89 "resolveSrv",
90 "resolveSoa",
91 "resolveNaptr",
92 "resolveCaa",
93 "resolveTlsa",
94 "resolveAny",
95 "reverse",
96 "getServers",
97 "setServers",
98 "getDefaultResultOrder",
99 "setDefaultResultOrder",
100];
101
102pub const RESOLVER_METHODS: &[&str] = &[
103 "getServers",
104 "setServers",
105 "resolve",
106 "resolve4",
107 "resolve6",
108 "resolveMx",
109 "resolveTxt",
110 "resolveCname",
111 "resolveNs",
112 "resolvePtr",
113 "resolveSrv",
114 "resolveSoa",
115 "resolveNaptr",
116 "resolveCaa",
117 "resolveTlsa",
118 "resolveAny",
119 "reverse",
120 "cancel",
121 "setLocalAddress",
122];
123
124pub fn call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
125 Some(match method {
126 "lookup" => lookup(args),
127 "lookupService" => lookup_service(args),
128 "resolve" => resolve_cb(args),
129 "resolve4" => cb_query(Query::A, args),
130 "resolve6" => cb_query(Query::Aaaa, args),
131 "resolveMx" => cb_query(Query::Mx, args),
132 "resolveTxt" => cb_query(Query::Txt, args),
133 "resolveCname" => cb_query(Query::Cname, args),
134 "resolveNs" => cb_query(Query::Ns, args),
135 "resolvePtr" => cb_query(Query::Ptr, args),
136 "resolveSrv" => cb_query(Query::Srv, args),
137 "resolveSoa" => cb_query(Query::Soa, args),
138 "resolveNaptr" => cb_query(Query::Naptr, args),
139 "resolveCaa" => cb_query(Query::Caa, args),
140 "resolveTlsa" => cb_query(Query::Tlsa, args),
141 "resolveAny" => cb_query(Query::Any, args),
142 "reverse" => cb_query(Query::Reverse, args),
143 "getServers" => get_servers(),
144 "setServers" => set_servers(args),
145 "getDefaultResultOrder" => Ok(get_default_result_order()),
146 "setDefaultResultOrder" => set_default_result_order(args),
147 "promiseLookup" => promise_lookup(args),
148 "promiseLookupService" => promise_lookup_service(args),
149 "promiseResolve" => resolve_promise(args),
150 "promiseResolve4" => promise_query(Query::A, args),
151 "promiseResolve6" => promise_query(Query::Aaaa, args),
152 "promiseResolveMx" => promise_query(Query::Mx, args),
153 "promiseResolveTxt" => promise_query(Query::Txt, args),
154 "promiseResolveCname" => promise_query(Query::Cname, args),
155 "promiseResolveNs" => promise_query(Query::Ns, args),
156 "promiseResolvePtr" => promise_query(Query::Ptr, args),
157 "promiseResolveSrv" => promise_query(Query::Srv, args),
158 "promiseResolveSoa" => promise_query(Query::Soa, args),
159 "promiseResolveNaptr" => promise_query(Query::Naptr, args),
160 "promiseResolveCaa" => promise_query(Query::Caa, args),
161 "promiseResolveTlsa" => promise_query(Query::Tlsa, args),
162 "promiseResolveAny" => promise_query(Query::Any, args),
163 "promiseReverse" => promise_query(Query::Reverse, args),
164 _ => return None,
165 })
166}
167
168struct DnsState {
171 servers: Option<Vec<String>>,
173 order: String,
175}
176
177thread_local! {
178 static STATE: RefCell<DnsState> =
179 RefCell::new(DnsState { servers: None, order: "verbatim".into() });
180}
181
182fn module_servers() -> Option<Vec<String>> {
183 STATE.with(|s| s.borrow().servers.clone())
184}
185
186pub fn constant(name: &str) -> Option<Value> {
190 match name {
191 "promises" => Some(promises_object()),
192 "ADDRCONFIG" => Some(Value::Float(1024.0)),
193 "V4MAPPED" => Some(Value::Float(2048.0)),
194 "ALL" => Some(Value::Float(256.0)),
195 _ => code_constant(name).map(|c| with_host(|h| h.new_str(c))),
196 }
197}
198
199fn code_constant(name: &str) -> Option<&'static str> {
201 Some(match name {
202 "NODATA" => "ENODATA",
203 "FORMERR" => "EFORMERR",
204 "SERVFAIL" => "ESERVFAIL",
205 "NOTFOUND" => "ENOTFOUND",
206 "NOTIMP" => "ENOTIMP",
207 "REFUSED" => "EREFUSED",
208 "BADQUERY" => "EBADQUERY",
209 "BADNAME" => "EBADNAME",
210 "BADFAMILY" => "EBADFAMILY",
211 "BADRESP" => "EBADRESP",
212 "CONNREFUSED" => "ECONNREFUSED",
213 "TIMEOUT" => "ETIMEOUT",
214 "EOF" => "EOF",
215 "FILE" => "EFILE",
216 "NOMEM" => "ENOMEM",
217 "DESTRUCTION" => "EDESTRUCTION",
218 "BADSTR" => "EBADSTR",
219 "BADFLAGS" => "EBADFLAGS",
220 "NONAME" => "ENONAME",
221 "BADHINTS" => "EBADHINTS",
222 "NOTINITIALIZED" => "ENOTINITIALIZED",
223 "LOADIPHLPAPI" => "ELOADIPHLPAPI",
224 "ADDRGETNETWORKPARAMS" => "EADDRGETNETWORKPARAMS",
225 "CANCELLED" => "ECANCELLED",
226 _ => return None,
227 })
228}
229
230const PROMISE_MEMBERS: &[(&str, &str)] = &[
232 ("lookup", "promiseLookup"),
233 ("lookupService", "promiseLookupService"),
234 ("resolve", "promiseResolve"),
235 ("resolve4", "promiseResolve4"),
236 ("resolve6", "promiseResolve6"),
237 ("resolveMx", "promiseResolveMx"),
238 ("resolveTxt", "promiseResolveTxt"),
239 ("resolveCname", "promiseResolveCname"),
240 ("resolveNs", "promiseResolveNs"),
241 ("resolvePtr", "promiseResolvePtr"),
242 ("resolveSrv", "promiseResolveSrv"),
243 ("resolveSoa", "promiseResolveSoa"),
244 ("resolveNaptr", "promiseResolveNaptr"),
245 ("resolveCaa", "promiseResolveCaa"),
246 ("resolveTlsa", "promiseResolveTlsa"),
247 ("resolveAny", "promiseResolveAny"),
248 ("reverse", "promiseReverse"),
249 ("getServers", "getServers"),
250 ("setServers", "setServers"),
251 ("getDefaultResultOrder", "getDefaultResultOrder"),
252 ("setDefaultResultOrder", "setDefaultResultOrder"),
253];
254
255fn promises_object() -> Value {
258 with_host(|h| {
259 let mut m = IndexMap::new();
260 for (key, target) in PROMISE_MEMBERS {
261 let b = h.alloc(JsObj::Builtin(format!("dns.{target}")));
262 m.insert((*key).into(), b);
263 }
264 h.new_object(m)
265 })
266}
267
268fn lookup(args: &[Value]) -> Result<Value, String> {
272 match args.first() {
275 None | Some(Value::Undef) => {}
276 Some(v) if with_host(|h| h.as_str(v)).is_some() => {}
277 Some(v) => {
278 return Err(crate::host::invalid_arg_type(
279 "hostname", "argument", "string", v,
280 ))
281 }
282 }
283 let hostname = arg_str(args, 0);
284 let want = opt_family(args);
285 let Some(cb) = args.last().cloned() else {
286 return Ok(Value::Undef);
287 };
288 match pick_addr(&hostname, want) {
289 Some(ip) => {
290 let family = if ip.is_ipv4() { 4.0 } else { 6.0 };
291 let (err, address) = with_host(|h| (h.null(), h.new_str(ip.to_string())));
292 crate::host::invoke(&cb, vec![err, address, Value::Float(family)], None)?;
293 }
294 None => {
295 let err = dns_error("ENOTFOUND", "getaddrinfo", &hostname);
296 crate::host::invoke(&cb, vec![err], None)?;
297 }
298 }
299 Ok(Value::Undef)
300}
301
302fn promise_lookup(args: &[Value]) -> Result<Value, String> {
304 let hostname = arg_str(args, 0);
305 let want = opt_family(args);
306 let p = with_host(|h| h.new_promise());
307 let id = with_host(|h| h.promise_id(&p)).unwrap_or(0);
308 match pick_addr(&hostname, want) {
309 Some(ip) => {
310 let family = if ip.is_ipv4() { 4.0 } else { 6.0 };
311 let obj = with_host(|h| {
312 let mut m = IndexMap::new();
313 m.insert("address".into(), h.new_str(ip.to_string()));
314 m.insert("family".into(), Value::Float(family));
315 h.new_object(m)
316 });
317 crate::host::resolve_promise_val(id, obj);
318 }
319 None => {
320 crate::host::reject_promise_val(id, dns_error("ENOTFOUND", "getaddrinfo", &hostname))
321 }
322 }
323 Ok(p)
324}
325
326fn pick_addr(hostname: &str, want: Option<u8>) -> Option<IpAddr> {
328 (hostname, 0u16)
329 .to_socket_addrs()
330 .ok()?
331 .map(|sa| sa.ip())
332 .find(|ip| match want {
333 Some(4) => ip.is_ipv4(),
334 Some(6) => ip.is_ipv6(),
335 _ => true,
336 })
337}
338
339fn opt_family(args: &[Value]) -> Option<u8> {
341 if args.len() < 3 {
342 return None;
343 }
344 with_host(|h| match h.get(&args[1]) {
345 Some(JsObj::Object(p)) => p.get("family").map(|f| h.to_number(f) as u8),
346 _ => {
347 let n = h.to_number(&args[1]);
348 (n.is_finite() && n > 0.0).then_some(n as u8)
349 }
350 })
351}
352
353fn lookup_service(args: &[Value]) -> Result<Value, String> {
357 let addr = arg_str(args, 0);
358 let port = super::arg_num(args, 1);
359 let Some(cb) = args.last().cloned() else {
360 return Ok(Value::Undef);
361 };
362 let servers = module_servers();
363 with_host(|h| h.incr_handle());
364 let tx = with_host(|h| h.io_sender());
365 std::thread::spawn(move || {
366 let result = run_lookup_service(&addr, port, servers.as_deref());
367 let label = addr;
368 let _ = tx.send(Box::new(move || {
369 with_host(|h| h.decr_handle());
370 let call_args = match result {
371 Ok((host, service)) => {
372 let (n, hv, sv) =
373 with_host(|h| (h.null(), h.new_str(host), h.new_str(service)));
374 vec![n, hv, sv]
375 }
376 Err(code) => vec![dns_error(&code, "getnameinfo", &label)],
377 };
378 if let Err(e) = crate::host::invoke(&cb, call_args, None) {
379 eprintln!("{e}");
380 }
381 Ok(())
382 }));
383 });
384 Ok(Value::Undef)
385}
386
387fn promise_lookup_service(args: &[Value]) -> Result<Value, String> {
389 let addr = arg_str(args, 0);
390 let port = super::arg_num(args, 1);
391 let servers = module_servers();
392 let p = with_host(|h| h.new_promise());
393 let id = with_host(|h| h.promise_id(&p)).unwrap_or(0);
394 with_host(|h| h.incr_handle());
395 let tx = with_host(|h| h.io_sender());
396 std::thread::spawn(move || {
397 let result = run_lookup_service(&addr, port, servers.as_deref());
398 let label = addr;
399 let _ = tx.send(Box::new(move || {
400 with_host(|h| h.decr_handle());
401 match result {
402 Ok((host, service)) => {
403 let obj = with_host(|h| {
404 let mut m = IndexMap::new();
405 m.insert("hostname".into(), h.new_str(host));
406 m.insert("service".into(), h.new_str(service));
407 h.new_object(m)
408 });
409 crate::host::resolve_promise_val(id, obj);
410 }
411 Err(code) => {
412 crate::host::reject_promise_val(id, dns_error(&code, "getnameinfo", &label))
413 }
414 }
415 Ok(())
416 }));
417 });
418 Ok(p)
419}
420
421fn run_lookup_service(
422 addr: &str,
423 port: f64,
424 servers: Option<&[String]>,
425) -> Result<(String, String), String> {
426 let ip: IpAddr = addr.parse().map_err(|_| "EINVAL".to_string())?;
427 let resolver = build_resolver(servers)?;
428 let lookup = resolver
429 .reverse_lookup(ip)
430 .map_err(|e| err_code(&e).to_string())?;
431 let host = lookup
432 .iter()
433 .next()
434 .map(|n| name_str(n))
435 .ok_or_else(|| "ENOTFOUND".to_string())?;
436 Ok((host, port_service(port as u16)))
437}
438
439fn port_service(port: u16) -> String {
442 let name = match port {
443 20 => "ftp-data",
444 21 => "ftp",
445 22 => "ssh",
446 23 => "telnet",
447 25 => "smtp",
448 53 => "domain",
449 67 => "bootps",
450 68 => "bootpc",
451 69 => "tftp",
452 79 => "finger",
453 80 => "http",
454 110 => "pop3",
455 111 => "sunrpc",
456 119 => "nntp",
457 123 => "ntp",
458 143 => "imap",
459 161 => "snmp",
460 194 => "irc",
461 389 => "ldap",
462 443 => "https",
463 445 => "microsoft-ds",
464 465 => "submissions",
465 514 => "shell",
466 515 => "printer",
467 587 => "submission",
468 631 => "ipp",
469 636 => "ldaps",
470 993 => "imaps",
471 995 => "pop3s",
472 1194 => "openvpn",
473 1433 => "ms-sql-s",
474 3306 => "mysql",
475 3389 => "ms-wbt-server",
476 5432 => "postgresql",
477 5060 => "sip",
478 5061 => "sips",
479 6379 => "redis",
480 8080 => "http-alt",
481 8443 => "https-alt",
482 _ => return port.to_string(),
483 };
484 name.to_string()
485}
486
487#[derive(Clone, Copy)]
490enum Query {
491 A,
492 Aaaa,
493 Mx,
494 Txt,
495 Cname,
496 Ns,
497 Ptr,
498 Srv,
499 Soa,
500 Naptr,
501 Caa,
502 Tlsa,
503 Any,
504 Reverse,
505}
506
507impl Query {
508 fn syscall(self) -> &'static str {
510 match self {
511 Query::A => "queryA",
512 Query::Aaaa => "queryAaaa",
513 Query::Mx => "queryMx",
514 Query::Txt => "queryTxt",
515 Query::Cname => "queryCname",
516 Query::Ns => "queryNs",
517 Query::Ptr => "queryPtr",
518 Query::Srv => "querySrv",
519 Query::Soa => "querySoa",
520 Query::Naptr => "queryNaptr",
521 Query::Caa => "queryCaa",
522 Query::Tlsa => "queryTlsa",
523 Query::Any => "queryAny",
524 Query::Reverse => "getHostByAddr",
525 }
526 }
527}
528
529fn query_of(rrtype: &str) -> Option<Query> {
530 Some(match rrtype {
531 "A" => Query::A,
532 "AAAA" => Query::Aaaa,
533 "MX" => Query::Mx,
534 "TXT" => Query::Txt,
535 "CNAME" => Query::Cname,
536 "NS" => Query::Ns,
537 "PTR" => Query::Ptr,
538 "SRV" => Query::Srv,
539 "SOA" => Query::Soa,
540 "NAPTR" => Query::Naptr,
541 "CAA" => Query::Caa,
542 "TLSA" => Query::Tlsa,
543 "ANY" => Query::Any,
544 _ => return None,
545 })
546}
547
548fn cb_query(q: Query, args: &[Value]) -> Result<Value, String> {
549 let name = arg_str(args, 0);
550 let Some(cb) = args.last().cloned() else {
551 return Ok(Value::Undef);
552 };
553 start_query(q, name, module_servers(), cb);
554 Ok(Value::Undef)
555}
556
557fn promise_query(q: Query, args: &[Value]) -> Result<Value, String> {
558 let name = arg_str(args, 0);
559 Ok(start_promise_query(q, name, module_servers()))
560}
561
562fn resolve_cb(args: &[Value]) -> Result<Value, String> {
564 let name = arg_str(args, 0);
565 let Some(cb) = args.last().cloned() else {
566 return Ok(Value::Undef);
567 };
568 let rrtype = if args.len() > 2 {
569 arg_str(args, 1)
570 } else {
571 "A".into()
572 };
573 let q = query_of(&rrtype).unwrap_or(Query::A);
574 start_query(q, name, module_servers(), cb);
575 Ok(Value::Undef)
576}
577
578fn resolve_promise(args: &[Value]) -> Result<Value, String> {
580 let name = arg_str(args, 0);
581 let rrtype = if args.len() > 1 {
582 arg_str(args, 1)
583 } else {
584 "A".into()
585 };
586 let q = query_of(&rrtype).unwrap_or(Query::A);
587 Ok(start_promise_query(q, name, module_servers()))
588}
589
590fn start_query(q: Query, name: String, servers: Option<Vec<String>>, cb: Value) {
593 with_host(|h| h.incr_handle());
594 let tx = with_host(|h| h.io_sender());
595 std::thread::spawn(move || {
596 let res = run_query(q, &name, servers.as_deref());
597 let label = name;
598 let _ = tx.send(Box::new(move || {
599 with_host(|h| h.decr_handle());
600 let call_args = match res {
601 Ok(data) => {
602 let val = dns_to_value(data);
603 let n = with_host(|h| h.null());
604 vec![n, val]
605 }
606 Err(code) => vec![dns_error(&code, q.syscall(), &label)],
607 };
608 if let Err(e) = crate::host::invoke(&cb, call_args, None) {
609 eprintln!("{e}");
610 }
611 Ok(())
612 }));
613 });
614}
615
616fn start_promise_query(q: Query, name: String, servers: Option<Vec<String>>) -> Value {
617 let p = with_host(|h| h.new_promise());
618 let id = with_host(|h| h.promise_id(&p)).unwrap_or(0);
619 with_host(|h| h.incr_handle());
620 let tx = with_host(|h| h.io_sender());
621 std::thread::spawn(move || {
622 let res = run_query(q, &name, servers.as_deref());
623 let label = name;
624 let _ = tx.send(Box::new(move || {
625 with_host(|h| h.decr_handle());
626 match res {
627 Ok(data) => crate::host::resolve_promise_val(id, dns_to_value(data)),
628 Err(code) => {
629 crate::host::reject_promise_val(id, dns_error(&code, q.syscall(), &label))
630 }
631 }
632 Ok(())
633 }));
634 });
635 p
636}
637
638fn run_or_promise(
640 q: Query,
641 name: String,
642 servers: Option<Vec<String>>,
643 cb: Option<Value>,
644 promises: bool,
645) -> Value {
646 if promises {
647 start_promise_query(q, name, servers)
648 } else if let Some(cb) = cb {
649 start_query(q, name, servers, cb);
650 Value::Undef
651 } else {
652 Value::Undef
653 }
654}
655
656enum DnsResult {
661 Strings(Vec<String>),
662 Mx(Vec<(u16, String)>),
663 Txt(Vec<Vec<String>>),
664 Srv(Vec<SrvRec>),
665 Soa(SoaRec),
666 Naptr(Vec<NaptrRec>),
667 Caa(Vec<CaaRec>),
668 Tlsa(Vec<TlsaRec>),
669 Any(Vec<AnyRec>),
670}
671
672struct SoaRec {
673 nsname: String,
674 hostmaster: String,
675 serial: u32,
676 refresh: i32,
677 retry: i32,
678 expire: i32,
679 minttl: u32,
680}
681
682struct SrvRec {
683 priority: u16,
684 weight: u16,
685 port: u16,
686 name: String,
687}
688
689struct NaptrRec {
690 flags: String,
691 service: String,
692 regexp: String,
693 replacement: String,
694 order: u16,
695 preference: u16,
696}
697
698struct CaaRec {
699 critical: u16,
700 tag: String,
701 value: String,
702}
703
704struct TlsaRec {
705 cert_usage: u8,
706 selector: u8,
707 matching: u8,
708 data: Vec<u8>,
709}
710
711enum AnyRec {
712 A(String, u32),
713 Aaaa(String, u32),
714 Cname(String),
715 Mx(u16, String),
716 Ns(String),
717 Ptr(String),
718 Txt(Vec<String>),
719 Srv(SrvRec),
720 Soa(SoaRec),
721 Naptr(NaptrRec),
722 Caa(CaaRec),
723}
724
725fn run_query(q: Query, name: &str, servers: Option<&[String]>) -> Result<DnsResult, String> {
726 let r = build_resolver(servers)?;
727 let ec = |e: ResolveError| err_code(&e).to_string();
728 match q {
729 Query::A => {
730 let l = r.ipv4_lookup(name).map_err(ec)?;
731 Ok(DnsResult::Strings(
732 l.iter().map(|a| a.to_string()).collect(),
733 ))
734 }
735 Query::Aaaa => {
736 let l = r.ipv6_lookup(name).map_err(ec)?;
737 Ok(DnsResult::Strings(
738 l.iter().map(|a| a.to_string()).collect(),
739 ))
740 }
741 Query::Mx => {
742 let l = r.mx_lookup(name).map_err(ec)?;
743 Ok(DnsResult::Mx(
744 l.iter()
745 .map(|m| (m.preference(), name_str(m.exchange())))
746 .collect(),
747 ))
748 }
749 Query::Txt => {
750 let l = r.txt_lookup(name).map_err(ec)?;
751 let entries = l
752 .iter()
753 .map(|t| {
754 t.txt_data()
755 .iter()
756 .map(|b| String::from_utf8_lossy(b).into_owned())
757 .collect()
758 })
759 .collect();
760 Ok(DnsResult::Txt(entries))
761 }
762 Query::Cname => {
763 let l = r.lookup(name, RecordType::CNAME).map_err(ec)?;
764 let names = l
765 .iter()
766 .filter_map(|d| match d {
767 RData::CNAME(c) => Some(name_str(c)),
768 _ => None,
769 })
770 .collect();
771 Ok(DnsResult::Strings(names))
772 }
773 Query::Ns => {
774 let l = r.ns_lookup(name).map_err(ec)?;
775 Ok(DnsResult::Strings(l.iter().map(|n| name_str(n)).collect()))
776 }
777 Query::Ptr => {
778 let l = r.lookup(name, RecordType::PTR).map_err(ec)?;
779 let names = l
780 .iter()
781 .filter_map(|d| match d {
782 RData::PTR(p) => Some(name_str(p)),
783 _ => None,
784 })
785 .collect();
786 Ok(DnsResult::Strings(names))
787 }
788 Query::Srv => {
789 let l = r.srv_lookup(name).map_err(ec)?;
790 Ok(DnsResult::Srv(l.iter().map(srv_rec).collect()))
791 }
792 Query::Soa => {
793 let l = r.soa_lookup(name).map_err(ec)?;
794 let soa = l.iter().next().ok_or_else(|| "ENODATA".to_string())?;
795 Ok(DnsResult::Soa(soa_rec(soa)))
796 }
797 Query::Naptr => {
798 let l = r.lookup(name, RecordType::NAPTR).map_err(ec)?;
799 let recs = l
800 .iter()
801 .filter_map(|d| match d {
802 RData::NAPTR(n) => Some(naptr_rec(n)),
803 _ => None,
804 })
805 .collect();
806 Ok(DnsResult::Naptr(recs))
807 }
808 Query::Caa => {
809 let l = r.lookup(name, RecordType::CAA).map_err(ec)?;
810 let recs = l
811 .iter()
812 .filter_map(|d| match d {
813 RData::CAA(c) => Some(caa_rec(c)),
814 _ => None,
815 })
816 .collect();
817 Ok(DnsResult::Caa(recs))
818 }
819 Query::Tlsa => {
820 let l = r.tlsa_lookup(name).map_err(ec)?;
821 let recs = l
822 .iter()
823 .map(|t| TlsaRec {
824 cert_usage: t.cert_usage().into(),
825 selector: t.selector().into(),
826 matching: t.matching().into(),
827 data: t.cert_data().to_vec(),
828 })
829 .collect();
830 Ok(DnsResult::Tlsa(recs))
831 }
832 Query::Any => {
833 let l = r.lookup(name, RecordType::ANY).map_err(ec)?;
834 let recs = l.records().iter().filter_map(any_rec).collect();
835 Ok(DnsResult::Any(recs))
836 }
837 Query::Reverse => {
838 let ip: IpAddr = name.parse().map_err(|_| "EINVAL".to_string())?;
839 let l = r.reverse_lookup(ip).map_err(ec)?;
840 Ok(DnsResult::Strings(l.iter().map(|n| name_str(n)).collect()))
841 }
842 }
843}
844
845fn name_str(n: &Name) -> String {
848 n.to_ascii().trim_end_matches('.').to_string()
849}
850
851fn srv_rec(s: &hickory_resolver::proto::rr::rdata::SRV) -> SrvRec {
852 SrvRec {
853 priority: s.priority(),
854 weight: s.weight(),
855 port: s.port(),
856 name: name_str(s.target()),
857 }
858}
859
860fn soa_rec(s: &hickory_resolver::proto::rr::rdata::SOA) -> SoaRec {
861 SoaRec {
862 nsname: name_str(s.mname()),
863 hostmaster: name_str(s.rname()),
864 serial: s.serial(),
865 refresh: s.refresh(),
866 retry: s.retry(),
867 expire: s.expire(),
868 minttl: s.minimum(),
869 }
870}
871
872fn naptr_rec(n: &hickory_resolver::proto::rr::rdata::NAPTR) -> NaptrRec {
873 NaptrRec {
874 flags: String::from_utf8_lossy(n.flags()).into_owned(),
875 service: String::from_utf8_lossy(n.services()).into_owned(),
876 regexp: String::from_utf8_lossy(n.regexp()).into_owned(),
877 replacement: name_str(n.replacement()),
878 order: n.order(),
879 preference: n.preference(),
880 }
881}
882
883fn caa_rec(c: &hickory_resolver::proto::rr::rdata::CAA) -> CaaRec {
884 CaaRec {
885 critical: if c.issuer_critical() { 128 } else { 0 },
886 tag: caa_tag(c.tag()),
887 value: caa_value(c.value()),
888 }
889}
890
891fn caa_tag(p: &CaaProperty) -> String {
892 match p {
893 CaaProperty::Issue => "issue".into(),
894 CaaProperty::IssueWild => "issuewild".into(),
895 CaaProperty::Iodef => "iodef".into(),
896 CaaProperty::Unknown(s) => s.clone(),
897 }
898}
899
900fn caa_value(v: &CaaValue) -> String {
901 match v {
902 CaaValue::Issuer(name, kvs) => {
903 let mut s = name.as_ref().map(name_str).unwrap_or_default();
904 for kv in kvs {
905 s.push_str("; ");
906 s.push_str(kv.key());
907 s.push('=');
908 s.push_str(kv.value());
909 }
910 s
911 }
912 CaaValue::Url(u) => u.to_string(),
913 CaaValue::Unknown(b) => String::from_utf8_lossy(b).into_owned(),
914 }
915}
916
917fn any_rec(rec: &hickory_resolver::proto::rr::Record) -> Option<AnyRec> {
920 let ttl = rec.ttl();
921 Some(match rec.data()? {
922 RData::A(a) => AnyRec::A(a.to_string(), ttl),
923 RData::AAAA(a) => AnyRec::Aaaa(a.to_string(), ttl),
924 RData::CNAME(c) => AnyRec::Cname(name_str(c)),
925 RData::MX(m) => AnyRec::Mx(m.preference(), name_str(m.exchange())),
926 RData::NS(n) => AnyRec::Ns(name_str(n)),
927 RData::PTR(p) => AnyRec::Ptr(name_str(p)),
928 RData::TXT(t) => AnyRec::Txt(
929 t.txt_data()
930 .iter()
931 .map(|b| String::from_utf8_lossy(b).into_owned())
932 .collect(),
933 ),
934 RData::SRV(s) => AnyRec::Srv(srv_rec(s)),
935 RData::SOA(s) => AnyRec::Soa(soa_rec(s)),
936 RData::NAPTR(n) => AnyRec::Naptr(naptr_rec(n)),
937 RData::CAA(c) => AnyRec::Caa(caa_rec(c)),
938 _ => return None,
939 })
940}
941
942fn dns_to_value(res: DnsResult) -> Value {
945 match res {
946 DnsResult::Strings(v) => with_host(|h| {
947 let items: Vec<Value> = v.into_iter().map(|s| h.new_str(s)).collect();
948 h.new_array(items)
949 }),
950 DnsResult::Mx(v) => with_host(|h| {
951 let items: Vec<Value> = v
952 .into_iter()
953 .map(|(pri, ex)| {
954 let mut m = IndexMap::new();
955 m.insert("exchange".into(), h.new_str(ex));
956 m.insert("priority".into(), Value::Float(pri as f64));
957 m.insert("type".into(), h.new_str("MX"));
958 h.new_object(m)
959 })
960 .collect();
961 h.new_array(items)
962 }),
963 DnsResult::Txt(v) => with_host(|h| {
964 let items: Vec<Value> = v
965 .into_iter()
966 .map(|chunks| {
967 let inner: Vec<Value> = chunks.into_iter().map(|s| h.new_str(s)).collect();
968 h.new_array(inner)
969 })
970 .collect();
971 h.new_array(items)
972 }),
973 DnsResult::Srv(v) => with_host(|h| {
974 let items: Vec<Value> = v.iter().map(|s| srv_obj(h, s)).collect();
975 h.new_array(items)
976 }),
977 DnsResult::Soa(s) => with_host(|h| soa_obj(h, &s)),
978 DnsResult::Naptr(v) => with_host(|h| {
979 let items: Vec<Value> = v.iter().map(|n| naptr_obj(h, n)).collect();
980 h.new_array(items)
981 }),
982 DnsResult::Caa(v) => with_host(|h| {
983 let items: Vec<Value> = v.iter().map(|c| caa_obj(h, c)).collect();
984 h.new_array(items)
985 }),
986 DnsResult::Tlsa(v) => {
987 let mut items = Vec::with_capacity(v.len());
989 for t in v {
990 let data = super::buffer::from_bytes(&t.data);
991 let obj = with_host(|h| {
992 let mut m = IndexMap::new();
993 m.insert("certUsage".into(), Value::Float(t.cert_usage as f64));
994 m.insert("selector".into(), Value::Float(t.selector as f64));
995 m.insert("match".into(), Value::Float(t.matching as f64));
996 m.insert("data".into(), data);
997 h.new_object(m)
998 });
999 items.push(obj);
1000 }
1001 with_host(|h| h.new_array(items))
1002 }
1003 DnsResult::Any(v) => with_host(|h| {
1004 let items: Vec<Value> = v.into_iter().map(|rec| any_obj(h, rec)).collect();
1005 h.new_array(items)
1006 }),
1007 }
1008}
1009
1010fn srv_obj(h: &mut crate::host::JsHost, s: &SrvRec) -> Value {
1011 let mut m = IndexMap::new();
1012 m.insert("name".into(), h.new_str(s.name.clone()));
1013 m.insert("port".into(), Value::Float(s.port as f64));
1014 m.insert("priority".into(), Value::Float(s.priority as f64));
1015 m.insert("weight".into(), Value::Float(s.weight as f64));
1016 m.insert("type".into(), h.new_str("SRV"));
1017 h.new_object(m)
1018}
1019
1020fn soa_obj(h: &mut crate::host::JsHost, s: &SoaRec) -> Value {
1021 let mut m = IndexMap::new();
1022 m.insert("nsname".into(), h.new_str(s.nsname.clone()));
1023 m.insert("hostmaster".into(), h.new_str(s.hostmaster.clone()));
1024 m.insert("serial".into(), Value::Float(s.serial as f64));
1025 m.insert("refresh".into(), Value::Float(s.refresh as f64));
1026 m.insert("retry".into(), Value::Float(s.retry as f64));
1027 m.insert("expire".into(), Value::Float(s.expire as f64));
1028 m.insert("minttl".into(), Value::Float(s.minttl as f64));
1029 h.new_object(m)
1030}
1031
1032fn naptr_obj(h: &mut crate::host::JsHost, n: &NaptrRec) -> Value {
1033 let mut m = IndexMap::new();
1034 m.insert("flags".into(), h.new_str(n.flags.clone()));
1035 m.insert("service".into(), h.new_str(n.service.clone()));
1036 m.insert("regexp".into(), h.new_str(n.regexp.clone()));
1037 m.insert("replacement".into(), h.new_str(n.replacement.clone()));
1038 m.insert("order".into(), Value::Float(n.order as f64));
1039 m.insert("preference".into(), Value::Float(n.preference as f64));
1040 h.new_object(m)
1041}
1042
1043fn caa_obj(h: &mut crate::host::JsHost, c: &CaaRec) -> Value {
1044 let mut m = IndexMap::new();
1045 m.insert("critical".into(), Value::Float(c.critical as f64));
1046 m.insert("type".into(), h.new_str("CAA"));
1047 m.insert(c.tag.clone(), h.new_str(c.value.clone()));
1048 h.new_object(m)
1049}
1050
1051fn any_obj(h: &mut crate::host::JsHost, rec: AnyRec) -> Value {
1053 let mut m = IndexMap::new();
1054 match rec {
1055 AnyRec::A(addr, ttl) => {
1056 m.insert("type".into(), h.new_str("A"));
1057 m.insert("address".into(), h.new_str(addr));
1058 m.insert("ttl".into(), Value::Float(ttl as f64));
1059 }
1060 AnyRec::Aaaa(addr, ttl) => {
1061 m.insert("type".into(), h.new_str("AAAA"));
1062 m.insert("address".into(), h.new_str(addr));
1063 m.insert("ttl".into(), Value::Float(ttl as f64));
1064 }
1065 AnyRec::Cname(v) => {
1066 m.insert("type".into(), h.new_str("CNAME"));
1067 m.insert("value".into(), h.new_str(v));
1068 }
1069 AnyRec::Mx(pri, ex) => {
1070 m.insert("type".into(), h.new_str("MX"));
1071 m.insert("exchange".into(), h.new_str(ex));
1072 m.insert("priority".into(), Value::Float(pri as f64));
1073 }
1074 AnyRec::Ns(v) => {
1075 m.insert("type".into(), h.new_str("NS"));
1076 m.insert("value".into(), h.new_str(v));
1077 }
1078 AnyRec::Ptr(v) => {
1079 m.insert("type".into(), h.new_str("PTR"));
1080 m.insert("value".into(), h.new_str(v));
1081 }
1082 AnyRec::Txt(chunks) => {
1083 let entries: Vec<Value> = chunks.into_iter().map(|s| h.new_str(s)).collect();
1084 let arr = h.new_array(entries);
1085 m.insert("type".into(), h.new_str("TXT"));
1086 m.insert("entries".into(), arr);
1087 }
1088 AnyRec::Srv(s) => {
1089 m.insert("type".into(), h.new_str("SRV"));
1090 m.insert("priority".into(), Value::Float(s.priority as f64));
1091 m.insert("weight".into(), Value::Float(s.weight as f64));
1092 m.insert("port".into(), Value::Float(s.port as f64));
1093 m.insert("name".into(), h.new_str(s.name));
1094 }
1095 AnyRec::Soa(s) => return soa_with_type(h, &s),
1096 AnyRec::Naptr(n) => {
1097 m.insert("type".into(), h.new_str("NAPTR"));
1098 m.insert("flags".into(), h.new_str(n.flags));
1099 m.insert("service".into(), h.new_str(n.service));
1100 m.insert("regexp".into(), h.new_str(n.regexp));
1101 m.insert("replacement".into(), h.new_str(n.replacement));
1102 m.insert("order".into(), Value::Float(n.order as f64));
1103 m.insert("preference".into(), Value::Float(n.preference as f64));
1104 }
1105 AnyRec::Caa(c) => {
1106 m.insert("type".into(), h.new_str("CAA"));
1107 m.insert("critical".into(), Value::Float(c.critical as f64));
1108 m.insert(c.tag, h.new_str(c.value));
1109 }
1110 }
1111 h.new_object(m)
1112}
1113
1114fn soa_with_type(h: &mut crate::host::JsHost, s: &SoaRec) -> Value {
1115 let mut m = IndexMap::new();
1116 m.insert("type".into(), h.new_str("SOA"));
1117 m.insert("nsname".into(), h.new_str(s.nsname.clone()));
1118 m.insert("hostmaster".into(), h.new_str(s.hostmaster.clone()));
1119 m.insert("serial".into(), Value::Float(s.serial as f64));
1120 m.insert("refresh".into(), Value::Float(s.refresh as f64));
1121 m.insert("retry".into(), Value::Float(s.retry as f64));
1122 m.insert("expire".into(), Value::Float(s.expire as f64));
1123 m.insert("minttl".into(), Value::Float(s.minttl as f64));
1124 h.new_object(m)
1125}
1126
1127fn build_resolver(servers: Option<&[String]>) -> Result<Resolver, String> {
1132 match servers {
1133 Some(list) if !list.is_empty() => {
1134 let ips: Vec<IpAddr> = list.iter().filter_map(|s| parse_ip(s)).collect();
1135 if ips.is_empty() {
1136 return Err("EBADFAMILY".into());
1137 }
1138 let group = NameServerConfigGroup::from_ips_clear(&ips, 53, true);
1139 let cfg = ResolverConfig::from_parts(None, vec![], group);
1140 Resolver::new(cfg, ResolverOpts::default()).map_err(|e| e.to_string())
1141 }
1142 _ => Resolver::from_system_conf()
1143 .or_else(|_| Resolver::new(ResolverConfig::default(), ResolverOpts::default()))
1144 .map_err(|e| e.to_string()),
1145 }
1146}
1147
1148fn parse_ip(s: &str) -> Option<IpAddr> {
1150 s.parse::<IpAddr>()
1151 .ok()
1152 .or_else(|| s.parse::<std::net::SocketAddr>().ok().map(|sa| sa.ip()))
1153}
1154
1155fn system_servers() -> Vec<String> {
1157 match hickory_resolver::system_conf::read_system_conf() {
1158 Ok((cfg, _)) => {
1159 let mut out: Vec<String> = Vec::new();
1160 for ns in cfg.name_servers() {
1161 let ip = ns.socket_addr.ip().to_string();
1162 if !out.contains(&ip) {
1163 out.push(ip);
1164 }
1165 }
1166 out
1167 }
1168 Err(_) => Vec::new(),
1169 }
1170}
1171
1172fn err_code(e: &ResolveError) -> &'static str {
1174 match e.kind() {
1175 ResolveErrorKind::NoRecordsFound { response_code, .. } => {
1176 if *response_code == ResponseCode::NXDomain {
1177 "ENOTFOUND"
1178 } else {
1179 "ENODATA"
1180 }
1181 }
1182 ResolveErrorKind::NoConnections => "ESERVFAIL",
1183 ResolveErrorKind::Timeout => "ETIMEOUT",
1184 _ => "ESERVFAIL",
1185 }
1186}
1187
1188fn dns_error(code: &str, syscall: &str, host: &str) -> Value {
1191 let msg = format!("Error: {syscall} {code} {host}");
1192 with_host(|h| {
1193 let e = crate::builtins::synth_error(h, &msg);
1194 let cv = h.new_str(code.to_string());
1195 let sv = h.new_str(syscall.to_string());
1196 let hv = h.new_str(host.to_string());
1197 if let Some(JsObj::Object(p)) = h.get_mut(&e) {
1198 p.insert("code".into(), cv);
1199 p.insert("syscall".into(), sv);
1200 p.insert("hostname".into(), hv);
1201 }
1202 e
1203 })
1204}
1205
1206fn get_servers() -> Result<Value, String> {
1209 let list = module_servers().unwrap_or_else(system_servers);
1210 Ok(with_host(|h| {
1211 let items: Vec<Value> = list.into_iter().map(|s| h.new_str(s)).collect();
1212 h.new_array(items)
1213 }))
1214}
1215
1216fn set_servers(args: &[Value]) -> Result<Value, String> {
1217 let list = servers_from_arg(args.first());
1218 STATE.with(|s| s.borrow_mut().servers = Some(list));
1219 Ok(Value::Undef)
1220}
1221
1222fn servers_from_arg(v: Option<&Value>) -> Vec<String> {
1223 with_host(|h| match v.and_then(|v| h.get(v)) {
1224 Some(JsObj::Array(items)) => items.iter().map(|x| h.str_of(x)).collect(),
1225 _ => Vec::new(),
1226 })
1227}
1228
1229fn get_default_result_order() -> Value {
1230 let order = STATE.with(|s| s.borrow().order.clone());
1231 with_host(|h| h.new_str(order))
1232}
1233
1234fn set_default_result_order(args: &[Value]) -> Result<Value, String> {
1235 let order = arg_str(args, 0);
1236 if order == "ipv4first" || order == "verbatim" || order == "ipv6first" {
1237 STATE.with(|s| s.borrow_mut().order = order);
1238 }
1239 Ok(Value::Undef)
1240}
1241
1242pub fn construct_resolver(_args: &[Value]) -> Value {
1246 make_resolver_obj(false)
1247}
1248
1249pub fn construct_resolver_promises(_args: &[Value]) -> Value {
1251 make_resolver_obj(true)
1252}
1253
1254fn make_resolver_obj(promises: bool) -> Value {
1255 let empty = with_host(|h| h.new_array(Vec::new()));
1256 let tag = with_host(|h| h.new_str("Resolver"));
1257 with_host(|h| {
1258 let mut m = IndexMap::new();
1259 m.insert("@@native".into(), tag);
1260 m.insert("@@servers".into(), empty);
1261 if promises {
1262 m.insert("@@promises".into(), Value::Bool(true));
1263 }
1264 h.new_object(m)
1265 })
1266}
1267
1268pub fn resolver_instance_call(
1270 recv: &Value,
1271 method: &str,
1272 args: Vec<Value>,
1273) -> Result<Value, String> {
1274 let promises = recv_promises(recv);
1275 let servers = recv_servers(recv);
1276
1277 match method {
1278 "getServers" => {
1279 let list = servers.clone().unwrap_or_else(system_servers);
1280 return Ok(with_host(|h| {
1281 let items: Vec<Value> = list.into_iter().map(|s| h.new_str(s)).collect();
1282 h.new_array(items)
1283 }));
1284 }
1285 "setServers" => {
1286 let list = servers_from_arg(args.first());
1287 set_recv_servers(recv, &list);
1288 return Ok(Value::Undef);
1289 }
1290 "cancel" | "setLocalAddress" => return Ok(Value::Undef),
1291 _ => {}
1292 }
1293
1294 if method == "resolve" {
1295 let name = super::arg_str(&args, 0);
1296 let has_rrtype = if promises {
1297 args.len() > 1
1298 } else {
1299 args.len() > 2
1300 };
1301 let rrtype = if has_rrtype {
1302 super::arg_str(&args, 1)
1303 } else {
1304 "A".to_string()
1305 };
1306 let q = query_of(&rrtype).unwrap_or(Query::A);
1307 let cb = if promises { None } else { args.last().cloned() };
1308 return Ok(run_or_promise(q, name, servers, cb, promises));
1309 }
1310
1311 if let Some(q) = query_for_method(method) {
1312 let name = super::arg_str(&args, 0);
1313 let cb = if promises { None } else { args.last().cloned() };
1314 return Ok(run_or_promise(q, name, servers, cb, promises));
1315 }
1316
1317 Err(format!("TypeError: resolver.{method} is not a function"))
1318}
1319
1320fn query_for_method(method: &str) -> Option<Query> {
1321 Some(match method {
1322 "resolve4" => Query::A,
1323 "resolve6" => Query::Aaaa,
1324 "resolveMx" => Query::Mx,
1325 "resolveTxt" => Query::Txt,
1326 "resolveCname" => Query::Cname,
1327 "resolveNs" => Query::Ns,
1328 "resolvePtr" => Query::Ptr,
1329 "resolveSrv" => Query::Srv,
1330 "resolveSoa" => Query::Soa,
1331 "resolveNaptr" => Query::Naptr,
1332 "resolveCaa" => Query::Caa,
1333 "resolveTlsa" => Query::Tlsa,
1334 "resolveAny" => Query::Any,
1335 "reverse" => Query::Reverse,
1336 _ => return None,
1337 })
1338}
1339
1340fn recv_promises(recv: &Value) -> bool {
1341 with_host(|h| {
1342 matches!(
1343 h.get(recv),
1344 Some(JsObj::Object(p)) if matches!(p.get("@@promises"), Some(Value::Bool(true)))
1345 )
1346 })
1347}
1348
1349fn recv_servers(recv: &Value) -> Option<Vec<String>> {
1350 with_host(|h| {
1351 let JsObj::Object(p) = h.get(recv)? else {
1352 return None;
1353 };
1354 let arr = p.get("@@servers")?;
1355 let JsObj::Array(items) = h.get(arr)? else {
1356 return None;
1357 };
1358 let list: Vec<String> = items.iter().map(|x| h.str_of(x)).collect();
1359 if list.is_empty() {
1360 None
1361 } else {
1362 Some(list)
1363 }
1364 })
1365}
1366
1367fn set_recv_servers(recv: &Value, list: &[String]) {
1368 let arr = with_host(|h| {
1369 let items: Vec<Value> = list.iter().map(|s| h.new_str(s.clone())).collect();
1370 h.new_array(items)
1371 });
1372 with_host(|h| {
1373 if let Some(JsObj::Object(p)) = h.get_mut(recv) {
1374 p.insert("@@servers".into(), arr);
1375 }
1376 });
1377}