#[cfg(not(windows))]
use libc::getaddrinfo as sys_getaddrinfo;
use bun_core::ZBox;
use bun_dns::{Backend, Family, GetAddrInfo, GetAddrInfoResult, Options, Protocol, SocketType};
use mozjs::jsapi::*;
use mozjs::jsval::{Int32Value, JSVal, ObjectValue, StringValue, UndefinedValue};
use mozjs::rooted;
use mozjs::rust::wrappers2::{JS_DefineFunction, JS_DefineProperty3, JS_NewPlainObject};
use crate::bun_api::js_string_from_utf8;
use ::std::result::Result;
const JSFUN_CONSTRUCTOR: u32 = 0x400;
use bun_dns::addrinfo as platform_addrinfo;
#[cfg(windows)]
mod sys_getaddrinfo_win {
pub type ADDRINFOW = super::platform_addrinfo;
#[link(name = "ws2_32")]
unsafe extern "system" {
pub unsafe fn GetAddrInfoW(
pwNodeName: *const u16,
pwServiceName: *const u16,
pHints: *const ADDRINFOW,
ppResult: *mut *mut ADDRINFOW,
) -> core::ffi::c_int;
pub unsafe fn FreeAddrInfoW(pAddrInfo: *mut ADDRINFOW);
}
}
#[cfg(not(windows))]
unsafe fn platform_resolve(
host_arg: *const core::ffi::c_char,
hints: *const platform_addrinfo,
result: &mut *mut platform_addrinfo,
) -> core::ffi::c_int {
unsafe { sys_getaddrinfo(host_arg, ::std::ptr::null(), hints, result) }
}
#[cfg(windows)]
unsafe fn platform_resolve(
host_arg: *const u16,
hints: *const platform_addrinfo,
result: &mut *mut platform_addrinfo,
) -> core::ffi::c_int {
unsafe { sys_getaddrinfo_win::GetAddrInfoW(host_arg, ::std::ptr::null(), hints, result) }
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn free_chain(result: *mut platform_addrinfo) {
unsafe { bun_dns::freeaddrinfo(result) };
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum FamilyFilter {
Any,
V4,
V6,
}
impl FamilyFilter {
fn to_bun(self) -> Family {
match self {
FamilyFilter::Any => Family::Unspecified,
FamilyFilter::V4 => Family::Inet,
FamilyFilter::V6 => Family::Inet6,
}
}
fn marker(self) -> Option<i32> {
match self {
FamilyFilter::V4 => Some(4),
FamilyFilter::V6 => Some(6),
FamilyFilter::Any => None,
}
}
}
struct DnsRecord {
address: ::std::string::String,
family: i32,
ttl: i32,
}
fn render_cache_ip(ip: &bun_dns::cache::IpAddr) -> (::std::string::String, i32) {
match ip {
bun_dns::cache::IpAddr::V4(o) => (format!("{}.{}.{}.{}", o[0], o[1], o[2], o[3]), 4),
bun_dns::cache::IpAddr::V6(bytes) => {
let segs: [u16; 8] =
core::array::from_fn(|i| u16::from_be_bytes([bytes[i * 2], bytes[i * 2 + 1]]));
(::std::net::Ipv6Addr::from(segs).to_string(), 6)
}
}
}
fn render_address(addr: &bun_dns::Address) -> Option<(::std::string::String, i32)> {
if let Some(v4) = addr.as_in4() {
let octets: [u8; 4] = unsafe { *::std::ptr::addr_of!(v4.sin_addr).cast::<[u8; 4]>() };
return Some((format!("{}.{}.{}.{}", octets[0], octets[1], octets[2], octets[3]), 4));
}
if let Some(v6) = addr.as_in6() {
let bytes: [u8; 16] = unsafe { *::std::ptr::addr_of!(v6.sin6_addr).cast::<[u8; 16]>() };
let segs: [u16; 8] =
core::array::from_fn(|i| u16::from_be_bytes([bytes[i * 2], bytes[i * 2 + 1]]));
return Some((::std::net::Ipv6Addr::from(segs).to_string(), 6));
}
None
}
fn resolve_host(host: &str, filter: FamilyFilter) -> Result<Vec<DnsRecord>, ::std::string::String> {
let marker = filter.marker();
if let Some(addrs) = bun_dns::cache::lookup(host.as_bytes()) {
let records: Vec<DnsRecord> = addrs
.iter()
.map(render_cache_ip)
.filter(|(_, fam)| marker.is_none_or(|m| *fam == m))
.map(|(address, family)| DnsRecord { address, family, ttl: 0 })
.collect();
if !records.is_empty() {
return Ok(records);
}
}
let req = GetAddrInfo {
name: host.as_bytes().to_vec().into_boxed_slice(),
port: 0,
options: Options {
family: filter.to_bun(),
socktype: SocketType::Stream,
protocol: Protocol::Unspecified,
backend: Backend::Libc,
flags: 0,
},
};
#[cfg(not(windows))]
let host_c = match ::std::ffi::CString::new(host) {
Ok(c) => c,
Err(_) => return Err(resolve_error(&host, -1)),
};
#[cfg(not(windows))]
let host_arg = host_c.as_ptr();
#[cfg(windows)]
let host_arg: Vec<u16> = {
if host.as_bytes().contains(&0) {
return Err(resolve_error(&host, -1));
}
let mut w: Vec<u16> =
String::from_utf8_lossy(host.as_bytes()).encode_utf16().collect();
w.push(0);
w
};
let hints = req.options.to_libc();
let mut result_head: *mut platform_addrinfo = ::std::ptr::null_mut();
let rc = unsafe {
platform_resolve(
host_arg,
hints
.as_ref()
.map(|h| h as *const platform_addrinfo)
.unwrap_or(::std::ptr::null()),
&mut result_head,
)
};
if rc != 0 {
return Err(resolve_error(&host, rc));
}
if result_head.is_null() {
return Ok(Vec::new());
}
let mut records: Vec<DnsRecord> = Vec::new();
let mut cache_ips: Vec<bun_dns::cache::IpAddr> = Vec::new();
let mut cur = result_head;
while !cur.is_null() {
let ai = unsafe { &*cur };
if let Some(res) = GetAddrInfoResult::from_addr_info(ai) {
if let Some((address, family)) = render_address(&res.address) {
if marker.is_none_or(|m| family == m) {
records.push(DnsRecord { address, family, ttl: 0 });
}
}
if filter == FamilyFilter::Any {
if let Some(v4) = res.address.as_in4() {
let octets: [u8; 4] =
unsafe { *::std::ptr::addr_of!(v4.sin_addr).cast::<[u8; 4]>() };
cache_ips.push(bun_dns::cache::IpAddr::V4(octets));
} else if let Some(v6) = res.address.as_in6() {
let bytes: [u8; 16] =
unsafe { *::std::ptr::addr_of!(v6.sin6_addr).cast::<[u8; 16]>() };
cache_ips.push(bun_dns::cache::IpAddr::V6(bytes));
}
}
}
cur = ai.ai_next;
}
unsafe { free_chain(result_head) };
if filter == FamilyFilter::Any {
bun_dns::cache::insert(host.as_bytes(), cache_ips, None);
}
Ok(records)
}
fn resolve_error(host: &str, rc: core::ffi::c_int) -> ::std::string::String {
format!("getaddrinfo failed for \"{host}\" (rc={rc})")
}
enum ResolveFailure {
InvalidArg(::std::string::String),
Resolver(::std::string::String),
}
impl ResolveFailure {
fn message(&self) -> &str {
match self {
ResolveFailure::InvalidArg(m) | ResolveFailure::Resolver(m) => m,
}
}
fn code(&self) -> &'static str {
match self {
ResolveFailure::InvalidArg(_) => "ERR_INVALID_ARG_TYPE",
ResolveFailure::Resolver(_) => "ENOTFOUND",
}
}
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn define_record(cx: *mut JSContext, arr: *mut JSObject, i: u32, rec: &DnsRecord) {
let mut wrapped = mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped;
rooted!(&in(cx_ref) let obj = JS_NewPlainObject(cx_ref));
if obj.get().is_null() {
return;
}
let js_addr = js_string_from_utf8(cx, &rec.address);
if !js_addr.is_null() {
rooted!(&in(cx_ref) let av = StringValue(&*js_addr));
JS_DefineProperty(
cx,
obj.handle().into(),
c"address".as_ptr(),
av.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
rooted!(&in(cx_ref) let fv = Int32Value(rec.family));
JS_DefineProperty(
cx,
obj.handle().into(),
c"family".as_ptr(),
fv.handle().into(),
JSPROP_ENUMERATE as u32,
);
rooted!(&in(cx_ref) let tv = Int32Value(rec.ttl));
JS_DefineProperty(
cx,
obj.handle().into(),
c"ttl".as_ptr(),
tv.handle().into(),
JSPROP_ENUMERATE as u32,
);
rooted!(&in(cx_ref) let ev = ObjectValue(obj.get()));
rooted!(&in(cx_ref) let arr_root = arr);
let _ = JS_DefineElement(
cx,
arr_root.handle().into(),
i,
ev.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn dns_resolve_body(
cx: *mut JSContext,
argc: u32,
args: &CallArgs,
filter: FamilyFilter,
) -> Result<*mut JSObject, ResolveFailure> {
if argc == 0 || !(*args.get(0).ptr).is_string() {
return Err(ResolveFailure::InvalidArg(
"Bun.DNSResolver.resolve expects a hostname string".to_string(),
));
}
let host = crate::js_to_rust_string(cx, *args.get(0).ptr);
if host.is_empty() {
return Err(ResolveFailure::InvalidArg(
"Bun.DNSResolver.resolve expects a non-empty hostname".to_string(),
));
}
let records = resolve_host(&host, filter).map_err(ResolveFailure::Resolver)?;
let mut wrapped = mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped;
rooted!(&in(cx_ref) let arr = mozjs::rust::wrappers2::NewArrayObject1(cx_ref, records.len()));
if arr.get().is_null() {
return Err(ResolveFailure::Resolver(
"Bun.DNSResolver: failed to allocate result array".to_string(),
));
}
for (i, rec) in records.iter().enumerate() {
define_record(cx, arr.get(), i as u32, rec);
}
Ok(arr.get())
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn make_error_value(cx: *mut JSContext, code: &str, msg: &str) -> JSVal {
let c_msg = ZBox::from_bytes(msg.as_bytes());
JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
let mut exn = UndefinedValue();
let got = JS_GetPendingException(
cx,
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut exn,
},
);
JS_ClearPendingException(cx);
if !got || !exn.is_object() {
return exn;
}
let mut wrapped = mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped;
rooted!(&in(cx_ref) let exn_obj = exn.to_object());
let c_code = ZBox::from_bytes(code.as_bytes());
let code_js = JS_NewStringCopyZ(cx, c_code.as_ptr());
if !code_js.is_null() {
rooted!(&in(cx_ref) let code_val = StringValue(&*code_js));
JS_DefineProperty(
cx,
exn_obj.handle().into(),
c"code".as_ptr(),
code_val.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
ObjectValue(exn_obj.get())
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn dns_resolve_promise(
cx: *mut JSContext,
argc: u32,
args: &CallArgs,
filter: FamilyFilter,
) -> bool {
let mut wrapped = mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped;
rooted!(&in(cx_ref) let promise = JS::NewPromiseObject(cx, HandleObject::null()));
if promise.get().is_null() {
JS_ClearPendingException(cx);
args.rval().set(UndefinedValue());
return true;
}
match dns_resolve_body(cx, argc, args, filter) {
Ok(arr) => {
rooted!(&in(cx_ref) let rv = ObjectValue(arr));
let _ = JS::ResolvePromise(cx, promise.handle().into(), rv.handle().into());
}
Err(fail) => {
let err_val = make_error_value(cx, fail.code(), fail.message());
rooted!(&in(cx_ref) let err_root = err_val);
let _ = JS::RejectPromise(cx, promise.handle().into(), err_root.handle().into());
}
}
args.rval().set(ObjectValue(promise.get()));
true
}
macro_rules! dns_resolve_native {
($name:ident, $filter:expr) => {
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn $name(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
dns_resolve_promise(cx, argc, &args, $filter)
}
};
}
dns_resolve_native!(dns_resolve, FamilyFilter::Any);
dns_resolve_native!(dns_resolve4, FamilyFilter::V4);
dns_resolve_native!(dns_resolve6, FamilyFilter::V6);
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn dns_resolver_ctor(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
let mut wrapped = mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped;
rooted!(&in(cx_ref) let g = JS_NewPlainObject(cx_ref));
if g.get().is_null() {
args.rval().set(UndefinedValue());
return true;
}
JS_DefineFunction(
cx_ref,
g.handle(),
c"resolve".as_ptr(),
Some(dns_resolve),
1,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx_ref,
g.handle(),
c"resolve4".as_ptr(),
Some(dns_resolve4),
1,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx_ref,
g.handle(),
c"resolve6".as_ptr(),
Some(dns_resolve6),
1,
JSPROP_ENUMERATE as u32,
);
args.rval().set(ObjectValue(g.get()));
true
}
pub unsafe fn install(
cx: &mut mozjs::context::JSContext,
bun_obj: mozjs::rust::Handle<*mut JSObject>,
) {
let ctor = JS_NewFunction(
cx.raw_cx(),
Some(dns_resolver_ctor),
0,
JSFUN_CONSTRUCTOR,
c"DNSResolver".as_ptr(),
);
if ctor.is_null() {
return;
}
let ctor_obj = JS_GetFunctionObject(ctor);
if ctor_obj.is_null() {
return;
}
rooted!(&in(cx) let ctor_root = ctor_obj);
JS_DefineProperty3(
cx,
bun_obj,
c"DNSResolver".as_ptr(),
ctor_root.handle(),
JSPROP_ENUMERATE as u32,
);
}