use std::{
any::Any,
cell::{Cell, RefCell},
collections::VecDeque,
ffi::{CStr, c_char, c_int, c_void},
net::{IpAddr, Ipv4Addr, Ipv6Addr},
panic::{AssertUnwindSafe, catch_unwind},
rc::Rc,
};
use cares_sys as sys;
use crate::{
error::{AresError, AresStatus, Result},
record::DnsRecordRef,
types::{HostEntry, NameInfo, ServerStateFlags},
};
pub type PanicPayload = Box<dyn Any + Send + 'static>;
#[derive(Default)]
pub struct CallbackState {
poisoned: Cell<bool>,
panic: RefCell<Option<PanicPayload>>,
depth: Cell<usize>,
completed: Cell<bool>,
}
impl CallbackState {
pub fn new() -> Self {
Self::default()
}
pub fn is_poisoned(&self) -> bool {
self.poisoned.get()
}
pub fn poisoned(&self) -> bool {
self.is_poisoned()
}
pub fn poison(&self, payload: PanicPayload) {
self.poisoned.set(true);
let mut stored = self.panic.borrow_mut();
if stored.is_none() {
*stored = Some(payload);
} else {
std::mem::forget(payload);
}
}
pub fn take_panic(&self) -> Option<PanicPayload> {
self.panic.borrow_mut().take()
}
pub fn enter(&self) {
self.depth.set(self.depth.get().saturating_add(1));
}
pub fn leave(&self) {
self.depth.set(self.depth.get().saturating_sub(1));
}
pub fn depth(&self) -> usize {
self.depth.get()
}
pub fn in_callback(&self) -> bool {
self.depth() != 0
}
pub fn mark_completed(&self) {
self.completed.set(true);
}
pub fn is_completed(&self) -> bool {
self.completed.get()
}
}
impl Drop for CallbackState {
fn drop(&mut self) {
if let Some(payload) = self.panic.get_mut().take()
&& let Err(payload) = catch_unwind(AssertUnwindSafe(|| drop(payload)))
{
std::mem::forget(payload);
}
}
}
pub struct CallbackSlot<F> {
callback: RefCell<Option<F>>,
state: Rc<CallbackState>,
completed: Cell<bool>,
}
impl<F> CallbackSlot<F> {
pub fn new(state: Rc<CallbackState>, callback: F) -> Rc<Self> {
Rc::new(Self {
callback: RefCell::new(Some(callback)),
state,
completed: Cell::new(false),
})
}
pub fn take(&self) -> Option<F> {
self.callback.borrow_mut().take()
}
pub fn state(&self) -> &Rc<CallbackState> {
&self.state
}
pub fn is_completed(&self) -> bool {
self.completed.get()
}
pub fn mark_completed(&self) {
self.completed.set(true);
self.state.mark_completed();
}
}
unsafe fn invoke_once<F, T>(arg: *mut c_void, timeouts: usize, prepare: impl FnOnce() -> Result<T>)
where
F: FnOnce(Result<T>, usize) + 'static,
{
if arg.is_null() {
return;
}
let slot = unsafe { Rc::from_raw(arg.cast::<CallbackSlot<F>>()) };
let state = Rc::clone(slot.state());
if let Some(callback) = slot.take() {
state.enter();
let suppressed = state.is_poisoned();
if let Err(payload) = catch_unwind(AssertUnwindSafe(move || {
if suppressed {
drop(callback);
} else {
callback(prepare(), timeouts);
}
})) {
state.poison(payload);
}
state.leave();
}
slot.mark_completed();
}
pub unsafe extern "C" fn dns_trampoline<F>(
arg: *mut c_void,
status: sys::ares_status_t,
timeouts: usize,
record: *const sys::ares_dns_record_t,
) where
F: for<'cb> FnOnce(Result<DnsRecordRef<'cb>>, usize) + 'static,
{
unsafe {
invoke_once::<F, _>(arg, timeouts, || {
let status = AresStatus::from(status);
if status != AresStatus::Success {
return Err(AresError::from_status(status));
}
if record.is_null() {
return Err(AresError::invalid_record(
"successful DNS callback has no record",
));
}
DnsRecordRef::from_raw(record)
});
}
}
pub use dns_trampoline as dns_rec_trampoline;
pub unsafe extern "C" fn host_trampoline<F>(
arg: *mut c_void,
status: c_int,
timeouts: c_int,
host: *mut libc::hostent,
) where
F: FnOnce(Result<HostEntry>, usize) + 'static,
{
unsafe {
invoke_once::<F, _>(arg, nonnegative_timeouts(timeouts), || {
let status = AresStatus::from_raw(status);
if status != AresStatus::Success {
return Err(AresError::from_status(status));
}
copy_host(host)
});
}
}
pub unsafe extern "C" fn nameinfo_trampoline<F>(
arg: *mut c_void,
status: c_int,
timeouts: c_int,
node: *mut c_char,
service: *mut c_char,
) where
F: FnOnce(Result<NameInfo>, usize) + 'static,
{
unsafe {
invoke_once::<F, _>(arg, nonnegative_timeouts(timeouts), || {
let status = AresStatus::from_raw(status);
if status != AresStatus::Success {
return Err(AresError::from_status(status));
}
Ok(NameInfo {
node: copy_optional_string(node),
service: copy_optional_string(service),
})
});
}
}
fn nonnegative_timeouts(timeouts: c_int) -> usize {
usize::try_from(timeouts).unwrap_or_default()
}
unsafe fn copy_optional_string(value: *const c_char) -> Option<String> {
if value.is_null() {
None
} else {
Some(
unsafe { CStr::from_ptr(value) }
.to_string_lossy()
.into_owned(),
)
}
}
unsafe fn copy_host(host: *const libc::hostent) -> Result<HostEntry> {
if host.is_null() {
return Err(AresError::invalid_record(
"successful host callback has no host entry",
));
}
let host = unsafe { &*host };
let name = unsafe { copy_optional_string(host.h_name) }.unwrap_or_default();
let mut aliases = Vec::new();
if !host.h_aliases.is_null() {
let mut current = host.h_aliases;
loop {
let value = unsafe { *current };
if value.is_null() {
break;
}
if let Some(value) = unsafe { copy_optional_string(value) } {
aliases.push(value);
}
current = unsafe { current.add(1) };
}
}
let length = match (host.h_addrtype, host.h_length) {
(libc::AF_INET, 4) => 4,
(libc::AF_INET6, 16) => 16,
_ => {
return Err(AresError::invalid_record(
"unsupported host address family or length",
));
}
};
let mut addresses = Vec::new();
if !host.h_addr_list.is_null() {
let mut current = host.h_addr_list;
loop {
let value = unsafe { *current };
if value.is_null() {
break;
}
let bytes = unsafe { std::slice::from_raw_parts(value.cast::<u8>(), length) };
let address = if length == 4 {
IpAddr::V4(Ipv4Addr::new(bytes[0], bytes[1], bytes[2], bytes[3]))
} else {
let mut octets = [0; 16];
octets.copy_from_slice(bytes);
IpAddr::V6(Ipv6Addr::from(octets))
};
addresses.push(address);
current = unsafe { current.add(1) };
}
}
Ok(HostEntry {
name,
aliases,
addresses,
})
}
#[derive(Clone, Debug)]
pub struct ServerStateEvent {
pub server: Option<String>,
pub success: bool,
pub flags: ServerStateFlags,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct SocketStateEvent {
pub socket: sys::ares_socket_t,
pub readable: bool,
pub writable: bool,
}
pub struct NotificationSlot<F, E> {
callback: RefCell<Option<F>>,
pending: RefCell<VecDeque<E>>,
dispatching: Cell<bool>,
state: Rc<CallbackState>,
}
impl<F, E> NotificationSlot<F, E>
where
F: FnMut(E),
{
pub fn new(state: Rc<CallbackState>, callback: F) -> Rc<Self> {
Rc::new(Self {
callback: RefCell::new(Some(callback)),
pending: RefCell::new(VecDeque::new()),
dispatching: Cell::new(false),
state,
})
}
fn deliver(&self, event: E) {
self.pending.borrow_mut().push_back(event);
if self.dispatching.replace(true) {
return;
}
let callback = self.callback.borrow_mut().take();
if let Some(mut callback) = callback {
loop {
let event = self.pending.borrow_mut().pop_front();
let Some(event) = event else { break };
self.state.enter();
let result = catch_unwind(AssertUnwindSafe(|| callback(event)));
self.state.leave();
if let Err(payload) = result {
self.state.poison(payload);
}
if self.state.is_poisoned() {
if let Err(payload) = catch_unwind(AssertUnwindSafe(|| drop(callback))) {
self.state.poison(payload);
}
self.pending.borrow_mut().clear();
self.dispatching.set(false);
return;
}
}
*self.callback.borrow_mut() = Some(callback);
}
self.dispatching.set(false);
}
}
unsafe fn notify<F, E>(arg: *mut c_void, prepare: impl FnOnce() -> E)
where
F: FnMut(E),
{
if arg.is_null() {
return;
}
let slot = unsafe { &*arg.cast::<NotificationSlot<F, E>>() };
if slot.state.is_poisoned() {
return;
}
if let Err(payload) = catch_unwind(AssertUnwindSafe(|| slot.deliver(prepare()))) {
slot.dispatching.set(false);
slot.state.poison(payload);
}
}
pub unsafe extern "C" fn server_state_trampoline<F>(
server: *const c_char,
success: sys::ares_bool_t,
flags: c_int,
arg: *mut c_void,
) where
F: FnMut(ServerStateEvent),
{
unsafe {
notify::<F, _>(arg, || ServerStateEvent {
server: copy_optional_string(server),
success: success != sys::ARES_FALSE,
flags: ServerStateFlags::from_bits_retain(flags),
});
}
}
pub unsafe extern "C" fn sock_state_trampoline<F>(
arg: *mut c_void,
socket: sys::ares_socket_t,
readable: c_int,
writable: c_int,
) where
F: FnMut(SocketStateEvent),
{
unsafe {
notify::<F, _>(arg, || SocketStateEvent {
socket,
readable: readable != 0,
writable: writable != 0,
});
}
}
#[cfg(test)]
mod tests {
use super::*;
type Handler = Box<dyn for<'cb> FnOnce(Result<DnsRecordRef<'cb>>, usize)>;
fn finish(state: &Rc<CallbackState>, callback: Handler) -> bool {
let slot = CallbackSlot::new(Rc::clone(state), callback);
unsafe {
dns_trampoline::<Handler>(
Rc::into_raw(Rc::clone(&slot)).cast_mut().cast(),
sys::ARES_ENOTFOUND,
0,
std::ptr::null(),
);
}
slot.is_completed()
}
#[test]
fn nested_poison_discards_notifications_queued_before_the_panic() {
type Callback = Box<dyn FnMut(usize)>;
type Slot = NotificationSlot<Callback, usize>;
let state = Rc::new(CallbackState::new());
let weak: Rc<RefCell<std::rc::Weak<Slot>>> = Rc::default();
let weak_capture = Rc::clone(&weak);
let nested_state = Rc::clone(&state);
let calls = Rc::new(RefCell::new(Vec::new()));
let calls_capture = Rc::clone(&calls);
let callback: Callback = Box::new(move |event| {
calls_capture.borrow_mut().push(event);
if event == 1 {
let slot = match weak_capture.borrow().upgrade() {
Some(slot) => slot,
None => panic!("notification owner missing"),
};
slot.deliver(2);
assert!(finish(
&nested_state,
Box::new(|_, _| panic!("nested panic"))
));
}
});
let slot = NotificationSlot::new(Rc::clone(&state), callback);
*weak.borrow_mut() = Rc::downgrade(&slot);
slot.deliver(1);
assert_eq!(*calls.borrow(), [1]);
assert!(slot.pending.borrow().is_empty());
assert!(slot.callback.borrow().is_none());
assert!(state.is_poisoned());
assert_eq!(state.depth(), 0);
}
#[test]
fn poisoned_completions_suppress_user_code_and_catch_destructor_panics() {
struct PanicOnDrop;
impl Drop for PanicOnDrop {
fn drop(&mut self) {
panic!("disabled closure destructor");
}
}
let state = Rc::new(CallbackState::new());
state.poison(Box::new("original panic"));
let calls = Rc::new(Cell::new(0));
let capture = Rc::clone(&calls);
let dropper = PanicOnDrop;
assert!(finish(
&state,
Box::new(move |_, _| {
let _keep_alive = &dropper;
capture.set(capture.get() + 1);
})
));
assert_eq!(calls.get(), 0);
assert_eq!(state.depth(), 0);
let payload = state.take_panic();
assert_eq!(
payload.as_ref().and_then(|p| p.downcast_ref::<&str>()),
Some(&"original panic")
);
}
}