use std::collections::HashMap;
use std::ffi::CStr;
use std::ffi::CString;
use std::sync::Mutex;
use std::sync::OnceLock;
use std::thread;
use bun_dns::cache::{self as dns_cache, IpAddr};
use core::ffi::c_char;
use core::ffi::c_int;
use core::ffi::c_void;
#[repr(C)]
struct AddrInfoResultEntry {
info: libc::addrinfo,
storage: libc::sockaddr_storage,
}
#[repr(C)]
struct AddrInfoResult {
entries: *mut AddrInfoResultEntry,
error: c_int,
}
enum Notify {
Socket(*mut c_void),
Quic {
pc: *mut c_void,
notify: unsafe extern "C" fn(*mut c_void),
},
}
struct ReqState {
refs: usize,
completed: bool,
host: CString,
port: u16,
notify: Vec<Notify>,
error: c_int,
entries_buf: Vec<AddrInfoResultEntry>,
}
struct Request {
result_c: AddrInfoResult,
state: Mutex<ReqState>,
}
unsafe impl Send for Request {}
unsafe impl Sync for Request {}
#[derive(Clone, Copy, PartialEq, Eq)]
struct RequestPtr(*mut Request);
unsafe impl Send for RequestPtr {}
unsafe impl Sync for RequestPtr {}
static INFLIGHT: OnceLock<Mutex<HashMap<Box<str>, RequestPtr>>> = OnceLock::new();
fn inflight() -> &'static Mutex<HashMap<Box<str>, RequestPtr>> {
INFLIGHT.get_or_init(|| Mutex::new(HashMap::new()))
}
unsafe extern "C" {
unsafe fn us_internal_dns_callback(c: *mut c_void, req: *mut c_void);
unsafe fn us_internal_dns_callback_threadsafe(c: *mut c_void, req: *mut c_void);
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn Bun__addrinfo_get(
_loop: *mut c_void,
host: *const c_char,
port: u16,
ptr: *mut *mut c_void,
) -> c_int {
let host = unsafe { CStr::from_ptr(host) };
let mut completed_hit = false;
let req = get_or_start(host, port, &mut completed_hit);
unsafe { *ptr = req.cast::<c_void>() };
if completed_hit { 0 } else { 1 }
}
fn get_or_start(host: &CStr, port: u16, completed_hit: &mut bool) -> *mut Request {
if let Some(addrs) = dns_cache::lookup(host.to_bytes()) {
*completed_hit = true;
return Request::completed(addrs, port);
}
let key = dns_cache_key(host);
{
let inflight = inflight().lock().unwrap();
if let Some(&RequestPtr(req)) = inflight.get(&key) {
let mut st = unsafe { (*req).state.lock().unwrap() };
st.refs += 1;
return req;
}
}
let req = new_inflight(host, port);
inflight()
.lock()
.unwrap()
.insert(key.clone(), RequestPtr(req));
let worker_req = RequestPtr(req);
let spawned = thread::Builder::new()
.name("bao-dns-resolve".into())
.spawn(move || resolve_worker(worker_req, key));
if spawned.is_err() {
inflight().lock().unwrap().remove(&dns_cache_key(host));
let notify = complete(req, Vec::new(), libc::EAGAIN as c_int);
notify_all(notify);
}
req
}
fn new_inflight(host: &CStr, port: u16) -> *mut Request {
Box::into_raw(Box::new(Request {
result_c: AddrInfoResult {
entries: core::ptr::null_mut(),
error: 0,
},
state: Mutex::new(ReqState {
refs: 3,
completed: false,
host: host.to_owned(),
port,
notify: Vec::new(),
error: 0,
entries_buf: Vec::new(),
}),
}))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn Bun__addrinfo_set(req: *mut c_void, socket: *mut c_void) -> c_int {
let Some(r) = (unsafe { (req as *const Request).as_ref() }) else {
return 0;
};
let notify_now = {
let mut st = r.state.lock().unwrap();
if st.completed {
Some(Notify::Socket(socket))
} else {
st.notify.push(Notify::Socket(socket));
None
}
};
if notify_now.is_some() {
unsafe { us_internal_dns_callback(socket, req) };
}
0
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn Bun__addrinfo_cancel(req: *mut c_void, socket: *mut c_void) -> c_int {
let Some(r) = (unsafe { (req as *const Request).as_ref() }) else {
return 0;
};
let mut st = r.state.lock().unwrap();
if st.completed {
return 0;
}
let before = st.notify.len();
st.notify
.retain(|n| !matches!(n, Notify::Socket(s) if *s == socket));
(st.notify.len() != before) as c_int
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn Bun__addrinfo_freeRequest(req: *mut c_void, _error: c_int) {
let Some(r) = (unsafe { (req as *const Request).as_ref() }) else {
return;
};
let mut st = r.state.lock().unwrap();
st.refs -= 1;
if st.refs == 0 {
drop(st);
drop(unsafe { Box::from_raw(r as *const Request as *mut Request) });
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn Bun__addrinfo_getRequestResult(req: *mut c_void) -> *mut c_void {
let Some(r) = (unsafe { (req as *const Request).as_ref() }) else {
return core::ptr::null_mut();
};
(&raw const r.result_c) as *const AddrInfoResult as *mut AddrInfoResult as *mut c_void
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn Bun__addrinfo_registerQuic2(
req: *mut c_void,
pc: *mut c_void,
notify: Option<unsafe extern "C" fn(*mut c_void)>,
) {
let Some(cb) = notify else {
return;
};
let Some(r) = (unsafe { (req as *const Request).as_ref() }) else {
return;
};
let notify_now = {
let mut st = r.state.lock().unwrap();
if st.completed {
Some(Notify::Quic { pc, notify: cb })
} else {
st.notify.push(Notify::Quic { pc, notify: cb });
None
}
};
if let Some(Notify::Quic { pc, notify }) = notify_now {
unsafe { notify(pc) };
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn Bun__addrinfo_registerQuic(_req: *mut c_void, _pc: *mut c_void) {}
fn dns_cache_key(host: &CStr) -> Box<str> {
String::from_utf8_lossy(&host.to_bytes().to_ascii_lowercase()).into()
}
fn resolve_worker(worker_req: RequestPtr, key: Box<str>) {
let RequestPtr(req) = worker_req;
let (host, port) = {
let st = unsafe { (*req).state.lock().unwrap() };
(st.host.clone(), st.port)
};
let (entries, addrs, error) = resolve_getaddrinfo(&host, port);
if error == 0 && !addrs.is_empty() {
dns_cache::insert(host.to_bytes(), addrs, None);
}
inflight().lock().unwrap().remove(&key);
let notify = complete(req, entries, error);
notify_all(notify);
}
fn resolve_getaddrinfo(host: &CStr, port: u16) -> (Vec<AddrInfoResultEntry>, Vec<IpAddr>, c_int) {
let mut hints: libc::addrinfo = unsafe { core::mem::zeroed() };
hints.ai_family = libc::AF_UNSPEC;
hints.ai_socktype = libc::SOCK_STREAM;
hints.ai_flags = libc::AI_ADDRCONFIG;
let mut result: *mut libc::addrinfo = core::ptr::null_mut();
let mut rc =
unsafe { libc::getaddrinfo(host.as_ptr(), core::ptr::null(), &hints, &mut result) };
if rc == libc::EAI_NONAME {
hints.ai_flags &= !libc::AI_ADDRCONFIG;
rc = unsafe { libc::getaddrinfo(host.as_ptr(), core::ptr::null(), &hints, &mut result) };
}
if rc != 0 || result.is_null() {
if !result.is_null() {
unsafe { libc::freeaddrinfo(result) };
}
return (Vec::new(), Vec::new(), rc);
}
let (mut entries, mut addrs) = unsafe { collect_entries(result, port) };
unsafe { libc::freeaddrinfo(result) };
interleave_families(&mut entries, &mut addrs);
link_chain(&mut entries);
(entries, addrs, 0)
}
unsafe fn collect_entries(
head: *mut libc::addrinfo,
port: u16,
) -> (Vec<AddrInfoResultEntry>, Vec<IpAddr>) {
let mut entries: Vec<AddrInfoResultEntry> = Vec::new();
let mut addrs: Vec<IpAddr> = Vec::new();
let mut cur = head;
while !cur.is_null() {
let ai = unsafe { &*cur };
if !ai.ai_addr.is_null() {
let mut entry = AddrInfoResultEntry {
info: unsafe { core::ptr::read(ai) },
storage: unsafe { core::mem::zeroed() },
};
if let Some(ip) = unsafe { copy_sockaddr(ai, &mut entry, port) } {
addrs.push(ip);
entries.push(entry);
}
}
cur = ai.ai_next;
}
(entries, addrs)
}
unsafe fn copy_sockaddr(
ai: &libc::addrinfo,
entry: &mut AddrInfoResultEntry,
port: u16,
) -> Option<IpAddr> {
match ai.ai_family {
libc::AF_INET => {
let src = unsafe { &*(ai.ai_addr as *const libc::sockaddr_in) };
let dst = unsafe { &mut *((&raw mut entry.storage).cast::<libc::sockaddr_in>()) };
dst.sin_family = libc::AF_INET as libc::sa_family_t;
dst.sin_port = port.to_be();
dst.sin_addr = src.sin_addr;
entry.info.ai_family = libc::AF_INET;
entry.info.ai_addrlen = core::mem::size_of::<libc::sockaddr_in>() as libc::socklen_t;
Some(IpAddr::V4(src.sin_addr.s_addr.to_ne_bytes()))
}
libc::AF_INET6 => {
let src = unsafe { &*(ai.ai_addr as *const libc::sockaddr_in6) };
let dst = unsafe { &mut *((&raw mut entry.storage).cast::<libc::sockaddr_in6>()) };
dst.sin6_family = libc::AF_INET6 as libc::sa_family_t;
dst.sin6_port = port.to_be();
dst.sin6_addr = src.sin6_addr;
entry.info.ai_family = libc::AF_INET6;
entry.info.ai_addrlen = core::mem::size_of::<libc::sockaddr_in6>() as libc::socklen_t;
Some(IpAddr::V6(src.sin6_addr.s6_addr))
}
_ => None,
}
}
fn interleave_families(entries: &mut [AddrInfoResultEntry], addrs: &mut [IpAddr]) {
debug_assert_eq!(entries.len(), addrs.len());
let mut want = libc::AF_INET6;
for idx in 0..entries.len() {
if entries[idx].info.ai_family == want {
want = other_family(want);
continue;
}
for j in idx + 1..entries.len() {
if entries[j].info.ai_family == want {
entries.swap(idx, j);
addrs.swap(idx, j);
want = other_family(want);
break;
}
}
}
}
fn other_family(f: c_int) -> c_int {
if f == libc::AF_INET6 {
libc::AF_INET
} else {
libc::AF_INET6
}
}
fn link_chain(entries: &mut [AddrInfoResultEntry]) {
let len = entries.len();
let base = entries.as_mut_ptr();
for idx in 0..len {
let entry = unsafe { &mut *base.add(idx) };
entry.info.ai_canonname = core::ptr::null_mut();
entry.info.ai_addr = core::ptr::addr_of_mut!(entry.storage).cast();
if idx + 1 < len {
entry.info.ai_next = core::ptr::addr_of_mut!(entry.info);
entry.info.ai_next = unsafe { core::ptr::addr_of_mut!((*base.add(idx + 1)).info) };
} else {
entry.info.ai_next = core::ptr::null_mut();
}
}
}
fn complete(req: *mut Request, entries: Vec<AddrInfoResultEntry>, error: c_int) -> Vec<Notify> {
let mut st = unsafe { (*req).state.lock().unwrap() };
st.completed = true;
st.error = error;
st.entries_buf = entries;
let entries_ptr = if st.entries_buf.is_empty() {
core::ptr::null_mut()
} else {
st.entries_buf.as_mut_ptr()
};
unsafe {
(*req).result_c.entries = entries_ptr;
(*req).result_c.error = error;
}
let notify = core::mem::take(&mut st.notify);
st.refs -= 2;
if st.refs == 0 {
drop(st);
drop(unsafe { Box::from_raw(req) });
}
notify
}
impl Request {
fn completed(addrs: Vec<IpAddr>, port: u16) -> *mut Request {
let mut entries: Vec<AddrInfoResultEntry> =
addrs.iter().map(|ip| entry_from_ip(ip, port)).collect();
link_chain(&mut entries);
let entries_ptr = if entries.is_empty() {
core::ptr::null_mut()
} else {
entries.as_mut_ptr()
};
Box::into_raw(Box::new(Request {
result_c: AddrInfoResult {
entries: entries_ptr,
error: 0,
},
state: Mutex::new(ReqState {
refs: 1,
completed: true,
host: CString::default(),
port,
notify: Vec::new(),
error: 0,
entries_buf: entries,
}),
}))
}
}
fn notify_all(notify: Vec<Notify>) {
for owner in notify {
match owner {
Notify::Socket(socket) => {
unsafe { us_internal_dns_callback_threadsafe(socket, core::ptr::null_mut()) };
}
Notify::Quic { pc, notify } => {
unsafe { notify(pc) };
}
}
}
}
fn entry_from_ip(ip: &IpAddr, port: u16) -> AddrInfoResultEntry {
let mut entry = AddrInfoResultEntry {
info: unsafe { core::mem::zeroed() },
storage: unsafe { core::mem::zeroed() },
};
match ip {
IpAddr::V4(octets) => {
let dst = unsafe { &mut *((&raw mut entry.storage).cast::<libc::sockaddr_in>()) };
dst.sin_family = libc::AF_INET as libc::sa_family_t;
dst.sin_port = port.to_be();
dst.sin_addr.s_addr = u32::from_ne_bytes(*octets);
entry.info.ai_family = libc::AF_INET;
entry.info.ai_socktype = libc::SOCK_STREAM;
entry.info.ai_protocol = libc::IPPROTO_TCP;
entry.info.ai_addrlen = core::mem::size_of::<libc::sockaddr_in>() as libc::socklen_t;
}
IpAddr::V6(octets) => {
let dst = unsafe { &mut *((&raw mut entry.storage).cast::<libc::sockaddr_in6>()) };
dst.sin6_family = libc::AF_INET6 as libc::sa_family_t;
dst.sin6_port = port.to_be();
dst.sin6_addr.s6_addr = *octets;
entry.info.ai_family = libc::AF_INET6;
entry.info.ai_socktype = libc::SOCK_STREAM;
entry.info.ai_protocol = libc::IPPROTO_TCP;
entry.info.ai_addrlen = core::mem::size_of::<libc::sockaddr_in6>() as libc::socklen_t;
}
}
entry
}
#[cfg(test)]
mod tests {
use super::*;
static TEST_LOCK: Mutex<()> = Mutex::new(());
#[test]
fn completed_request_result_layout() {
let _guard = TEST_LOCK.lock().unwrap();
dns_cache::insert(b"layout.test", vec![IpAddr::V4([127, 0, 0, 1])], Some(60));
let mut hit = false;
let req = get_or_start(
CString::new("layout.test").unwrap().as_c_str(),
8080,
&mut hit,
);
assert!(hit);
let result = unsafe { Bun__addrinfo_getRequestResult(req as *mut c_void) };
assert!(!result.is_null());
let result = unsafe { &*(result as *const AddrInfoResult) };
assert_eq!(result.error, 0);
assert!(!result.entries.is_null());
let entry = unsafe { &*result.entries };
assert!(entry.info.ai_next.is_null());
assert_eq!(entry.info.ai_family, libc::AF_INET);
assert!(core::ptr::eq(
entry.info.ai_addr.cast::<u8>(),
(&raw const entry.storage).cast::<u8>()
));
let sa = unsafe { &*(entry.info.ai_addr as *const libc::sockaddr_in) };
assert_eq!(sa.sin_addr.s_addr, u32::from_ne_bytes([127, 0, 0, 1]));
assert_eq!(sa.sin_port, 8080u16.to_be());
unsafe { Bun__addrinfo_freeRequest(req as *mut c_void, 0) };
}
#[test]
fn link_chain_points_ai_addr_at_own_storage() {
let mut entries = vec![
entry_from_ip(&IpAddr::V4([10, 0, 0, 1]), 80),
entry_from_ip(&IpAddr::V6([0x20; 16]), 443),
];
link_chain(&mut entries);
for entry in &mut entries {
assert!(
core::ptr::eq(
entry.info.ai_addr.cast::<u8>(),
(&raw const entry.storage).cast::<u8>()
),
"ai_addr must point at the entry's own Vec storage, not a producer stack frame"
);
}
let sa4 = unsafe { &*(entries[0].info.ai_addr as *const libc::sockaddr_in) };
assert_eq!(sa4.sin_addr.s_addr, u32::from_ne_bytes([10, 0, 0, 1]));
assert_eq!(sa4.sin_port, 80u16.to_be());
let sa6 = unsafe { &*(entries[1].info.ai_addr as *const libc::sockaddr_in6) };
assert_eq!(sa6.sin6_addr.s6_addr, [0x20; 16]);
assert_eq!(sa6.sin6_port, 443u16.to_be());
}
#[test]
fn miss_creates_inflight_request() {
let _guard = TEST_LOCK.lock().unwrap();
let host = CString::new("definitely-miss-1.test").unwrap();
let req = new_inflight(host.as_c_str(), 443);
assert!(!req.is_null());
let key = dns_cache_key(&host);
inflight()
.lock()
.unwrap()
.insert(key.clone(), RequestPtr(req));
{
let inflight = inflight().lock().unwrap();
assert!(inflight.contains_key(&key));
}
let mut hit = false;
let req2 = get_or_start(host.as_c_str(), 443, &mut hit);
assert!(!hit);
assert_eq!(req, req2);
let notify = complete(req, Vec::new(), libc::EAI_NONAME);
assert!(notify.is_empty());
inflight().lock().unwrap().remove(&key);
unsafe {
Bun__addrinfo_freeRequest(req as *mut c_void, 0);
Bun__addrinfo_freeRequest(req2 as *mut c_void, 0);
}
}
#[test]
fn set_and_cancel_lifecycle() {
let _guard = TEST_LOCK.lock().unwrap();
let host = CString::new("definitely-miss-2.test").unwrap();
let req = new_inflight(host.as_c_str(), 443);
let key = dns_cache_key(&host);
inflight()
.lock()
.unwrap()
.insert(key.clone(), RequestPtr(req));
let fake_socket = 0x1000usize as *mut c_void;
assert_eq!(
unsafe { Bun__addrinfo_set(req as *mut c_void, fake_socket) },
0
);
assert_eq!(
unsafe { Bun__addrinfo_cancel(req as *mut c_void, fake_socket) },
1
);
assert_eq!(
unsafe { Bun__addrinfo_cancel(req as *mut c_void, fake_socket) },
0
);
unsafe { Bun__addrinfo_freeRequest(req as *mut c_void, 0) };
let notify = complete(req, Vec::new(), libc::EAI_NONAME);
assert!(notify.is_empty());
inflight().lock().unwrap().remove(&key);
}
#[test]
fn end_to_end_resolve_then_cache_hit() {
let _guard = TEST_LOCK.lock().unwrap();
let host = CString::new("localhost").unwrap();
let mut hit = false;
let req = get_or_start(host.as_c_str(), 80, &mut hit);
assert!(!hit);
let mut completed = false;
for _ in 0..1000 {
{
let st = unsafe { (*req).state.lock().unwrap() };
if st.completed {
completed = true;
break;
}
}
std::thread::sleep(std::time::Duration::from_millis(10));
}
assert!(completed, "resolver worker did not complete in time");
{
let st = unsafe { (*req).state.lock().unwrap() };
assert_eq!(st.error, 0);
assert!(!st.entries_buf.is_empty());
}
unsafe { Bun__addrinfo_freeRequest(req as *mut c_void, 0) };
assert!(dns_cache::lookup(b"localhost").is_some());
let req2 = get_or_start(host.as_c_str(), 80, &mut hit);
assert!(hit);
unsafe { Bun__addrinfo_freeRequest(req2 as *mut c_void, 0) };
}
#[test]
fn interleave_starts_v6() {
let mut entries = vec![
entry_from_ip(&IpAddr::V4([1, 1, 1, 1]), 0),
entry_from_ip(&IpAddr::V4([2, 2, 2, 2]), 0),
entry_from_ip(&IpAddr::V6([0x20; 16]), 0),
];
let mut addrs = vec![
IpAddr::V4([1, 1, 1, 1]),
IpAddr::V4([2, 2, 2, 2]),
IpAddr::V6([0x20; 16]),
];
interleave_families(&mut entries, &mut addrs);
assert_eq!(entries[0].info.ai_family, libc::AF_INET6);
assert_eq!(entries[1].info.ai_family, libc::AF_INET);
assert_eq!(entries[2].info.ai_family, libc::AF_INET);
assert!(matches!(addrs[0], IpAddr::V6(_)));
link_chain(&mut entries);
unsafe {
let next1 = (&raw mut entries[1].info) as *mut libc::addrinfo;
assert_eq!(entries[0].info.ai_next, next1);
assert!(entries[2].info.ai_next.is_null());
}
}
}