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