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;
#[cfg(not(windows))]
use libc::{
addrinfo, sockaddr_in, sockaddr_in6, sockaddr_storage, AI_ADDRCONFIG, AF_INET, AF_INET6,
AF_UNSPEC, EAI_NONAME, IPPROTO_TCP, SOCK_STREAM,
};
#[cfg(not(windows))]
use libc::{sa_family_t, socklen_t};
#[cfg(windows)]
use bun_windows_sys::ws2_32::{
addrinfo, sockaddr_in, sockaddr_in6, sockaddr_storage, AF_INET, AF_INET6, AF_UNSPEC,
IPPROTO_TCP, SOCK_STREAM,
};
#[cfg(windows)]
#[allow(non_camel_case_types)] type socklen_t = usize;
#[cfg(windows)]
#[allow(non_camel_case_types)] type sa_family_t = u16;
#[cfg(windows)]
mod win_dns {
use core::ffi::c_int;
pub type ADDRINFOW = super::addrinfo;
pub const AI_ADDRCONFIG: c_int = 0x400;
pub const EAI_NONAME: c_int = 11004;
#[link(name = "ws2_32")]
unsafe extern "system" {
pub unsafe fn GetAddrInfoW(
pwNodeName: *const u16,
pwServiceName: *const u16,
pHints: *const ADDRINFOW,
ppResult: *mut *mut ADDRINFOW,
) -> c_int;
pub unsafe fn FreeAddrInfoW(pAddrInfo: *mut ADDRINFOW);
}
}
#[cfg(windows)]
use win_dns::{AI_ADDRCONFIG, EAI_NONAME};
#[unsafe(no_mangle)]
pub unsafe extern "C" fn Bun__parseIpAddress(
host: *const c_char,
port: u16,
out: *mut sockaddr_storage,
) -> c_int {
if host.is_null() || out.is_null() {
return 0;
}
let bytes = unsafe { CStr::from_ptr(host) }.to_bytes();
let (addr_bytes, zone) = match bytes.iter().position(|&b| b == b'%') {
Some(at) => (&bytes[..at], Some(&bytes[at + 1..])),
None => (bytes, None),
};
let ip = match core::str::from_utf8(addr_bytes).ok().and_then(|s| s.parse::<std::net::IpAddr>().ok()) {
Some(ip) => ip,
None => return 0,
};
let scope_id = match (ip, zone) {
(std::net::IpAddr::V6(_), Some(zone)) => match core::str::from_utf8(zone).unwrap_or("").parse::<u32>() {
Ok(idx) => idx,
Err(_) => return 0,
},
_ => 0,
};
unsafe { core::ptr::write_bytes(out, 0, 1) };
match ip {
std::net::IpAddr::V4(octets) => {
let sin = unsafe { &mut *out.cast::<sockaddr_in>() };
sin.sin_family = AF_INET as sa_family_t;
sin.sin_port = port.to_be();
sin.sin_addr.s_addr = u32::from_ne_bytes(octets.octets());
}
std::net::IpAddr::V6(octets) => {
let sin6 = unsafe { &mut *out.cast::<sockaddr_in6>() };
sin6.sin6_family = AF_INET6 as sa_family_t;
sin6.sin6_port = port.to_be();
sin6.sin6_addr.s6_addr = octets.octets();
sin6.sin6_scope_id = scope_id;
}
}
1
}
#[repr(C)]
struct AddrInfoResultEntry {
info: addrinfo,
storage: 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: addrinfo = unsafe { core::mem::zeroed() };
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_ADDRCONFIG;
#[cfg(windows)]
let host_w: Vec<u16> = {
let mut w: Vec<u16> = String::from_utf8_lossy(host.to_bytes()).encode_utf16().collect();
w.push(0);
w
};
#[cfg(windows)]
let host_arg = host_w.as_ptr();
#[cfg(not(windows))]
let host_arg = host.as_ptr();
let mut result: *mut addrinfo = core::ptr::null_mut();
let mut rc =
unsafe { getaddrinfo(host_arg, core::ptr::null(), &hints, &mut result) };
if rc == EAI_NONAME {
hints.ai_flags &= !AI_ADDRCONFIG;
rc = unsafe { getaddrinfo(host_arg, core::ptr::null(), &hints, &mut result) };
}
if rc != 0 || result.is_null() {
if !result.is_null() {
unsafe { freeaddrinfo(result) };
}
return (Vec::new(), Vec::new(), rc);
}
let (mut entries, mut addrs) = unsafe { collect_entries(result, port) };
unsafe { freeaddrinfo(result) };
interleave_families(&mut entries, &mut addrs);
link_chain(&mut entries);
(entries, addrs, 0)
}
#[cfg(not(windows))]
unsafe fn freeaddrinfo(res: *mut addrinfo) {
unsafe { libc::freeaddrinfo(res) };
}
#[cfg(windows)]
unsafe fn freeaddrinfo(res: *mut addrinfo) {
unsafe { win_dns::FreeAddrInfoW(res) };
}
#[cfg(not(windows))]
unsafe fn getaddrinfo(
host_arg: *const c_char,
service: *const c_char,
hints: &addrinfo,
result: &mut *mut addrinfo,
) -> c_int {
unsafe { libc::getaddrinfo(host_arg, service, hints, result) }
}
#[cfg(windows)]
unsafe fn getaddrinfo(
host_arg: *const u16,
service: *const u16,
hints: &addrinfo,
result: &mut *mut addrinfo,
) -> c_int {
unsafe { win_dns::GetAddrInfoW(host_arg, service, hints, result) }
}
unsafe fn collect_entries(
head: *mut 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: &addrinfo,
entry: &mut AddrInfoResultEntry,
port: u16,
) -> Option<IpAddr> {
match ai.ai_family {
AF_INET => {
let src = unsafe { &*(ai.ai_addr as *const sockaddr_in) };
let dst = unsafe { &mut *((&raw mut entry.storage).cast::<sockaddr_in>()) };
dst.sin_family = AF_INET as sa_family_t;
dst.sin_port = port.to_be();
dst.sin_addr = src.sin_addr;
entry.info.ai_family = AF_INET;
entry.info.ai_addrlen = core::mem::size_of::<sockaddr_in>() as socklen_t;
Some(IpAddr::V4(src.sin_addr.s_addr.to_ne_bytes()))
}
AF_INET6 => {
let src = unsafe { &*(ai.ai_addr as *const sockaddr_in6) };
let dst = unsafe { &mut *((&raw mut entry.storage).cast::<sockaddr_in6>()) };
dst.sin6_family = AF_INET6 as sa_family_t;
dst.sin6_port = port.to_be();
dst.sin6_addr = src.sin6_addr;
entry.info.ai_family = AF_INET6;
entry.info.ai_addrlen = core::mem::size_of::<sockaddr_in6>() as 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 = 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 == AF_INET6 {
AF_INET
} else {
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::<sockaddr_in>()) };
dst.sin_family = AF_INET as sa_family_t;
dst.sin_port = port.to_be();
dst.sin_addr.s_addr = u32::from_ne_bytes(*octets);
entry.info.ai_family = AF_INET;
entry.info.ai_socktype = SOCK_STREAM;
entry.info.ai_protocol = IPPROTO_TCP;
entry.info.ai_addrlen = core::mem::size_of::<sockaddr_in>() as socklen_t;
}
IpAddr::V6(octets) => {
let dst = unsafe { &mut *((&raw mut entry.storage).cast::<sockaddr_in6>()) };
dst.sin6_family = AF_INET6 as sa_family_t;
dst.sin6_port = port.to_be();
dst.sin6_addr.s6_addr = *octets;
entry.info.ai_family = AF_INET6;
entry.info.ai_socktype = SOCK_STREAM;
entry.info.ai_protocol = IPPROTO_TCP;
entry.info.ai_addrlen = core::mem::size_of::<sockaddr_in6>() as 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, 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 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 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 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(), 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(), 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, AF_INET6);
assert_eq!(entries[1].info.ai_family, AF_INET);
assert_eq!(entries[2].info.ai_family, AF_INET);
assert!(matches!(addrs[0], IpAddr::V6(_)));
link_chain(&mut entries);
unsafe {
let next1 = (&raw mut entries[1].info) as *mut addrinfo;
assert_eq!(entries[0].info.ai_next, next1);
assert!(entries[2].info.ai_next.is_null());
}
}
}