#![no_std]
mod buf;
pub mod err;
use core::ffi::CStr;
use core::net::Ipv4Addr;
use core::net::Ipv6Addr;
use crate::buf::Gaih4Buf;
use crate::err::NssErr;
use crate::err::NssStatus;
#[doc(hidden)]
pub mod _macro_internal {
pub use paste;
}
#[macro_export]
macro_rules! impl_gethostbyname4_r {
($nss_name:ident, $resolver:ident) => {
$crate::_macro_internal::paste::paste! {
#[unsafe(no_mangle)]
pub unsafe extern "C" fn [<_nss_ $nss_name _gethostbyname4_r>](
name: *const ::libc::c_char,
pat: *mut *mut $crate::GaihAddrTuple,
buffer: *mut ::libc::c_char,
buflen: ::libc::size_t,
errnop: *mut ::libc::c_int,
h_errnop: *mut ::libc::c_int,
ttlp: *mut ::libc::c_int,
) -> ::libc::c_int {
unsafe { $crate::gethostbyname4_r::<$resolver>(name, pat, buffer, buflen, errnop, h_errnop, ttlp) }
}
}
};
}
#[inline]
pub unsafe fn gethostbyname4_r<R: HostResolver>(
name: *const libc::c_char,
pat: *mut *mut GaihAddrTuple,
buffer: *mut libc::c_char,
buflen: libc::size_t,
errnop: *mut libc::c_int,
h_errnop: *mut libc::c_int,
ttlp: *mut libc::c_int,
) -> libc::c_int {
if name.is_null() || pat.is_null() || buffer.is_null() || errnop.is_null() || h_errnop.is_null()
{
return NssStatus::Unavailable as i32;
}
let hostname = unsafe {
CStr::from_ptr(name)
};
let (pat, errnop, h_errnop) = unsafe {
(&mut *pat, &mut *errnop, &mut *h_errnop)
};
let maybe_buf = unsafe {
Gaih4Buf::try_new(hostname, pat, buffer, buflen)
};
let mut buffer = match maybe_buf {
Ok(b) => b,
Err(e) => return e.bail(errnop, h_errnop),
};
let Ok(hostname) = hostname.to_str() else {
return NssErr::INVALID_INPUT.bail(errnop, h_errnop);
};
let addrs = match R::resolve_host(hostname) {
Ok(res) => res,
Err(e) => return e.bail(errnop, h_errnop),
};
let mut count = 0;
for addr in addrs {
count += 1;
if !buffer.push(addr) {
return NssErr::BUF_TOO_SMALL.bail(errnop, h_errnop);
}
}
if count == 0 {
return NssErr::NO_RESULT.bail(errnop, h_errnop);
}
if !ttlp.is_null()
&& let Some(user_ttlp) = R::set_ttlp(hostname)
{
unsafe {
*ttlp = user_ttlp;
}
}
NssErr::SUCCESS.bail(errnop, h_errnop)
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Addr {
V4(Ipv4Addr),
V6 {
ip: Ipv6Addr,
scope_id: u32,
},
}
pub trait HostResolver {
fn resolve_host(hostname: &str) -> Result<impl IntoIterator<Item = Addr>, NssErr>;
fn set_ttlp(hostname: &str) -> Option<i32> {
let _ = hostname;
None
}
}
#[repr(C)]
#[derive(Debug)]
pub struct GaihAddrTuple {
next: *mut GaihAddrTuple,
name: *const libc::c_char,
family: libc::c_int,
addr: [libc::c_uint; 4],
scope_id: libc::c_uint,
}
impl GaihAddrTuple {
fn new(hostname: *const libc::c_char) -> Self {
Self {
next: core::ptr::null_mut(),
name: hostname,
family: libc::AF_UNSPEC,
addr: [0u32; 4],
scope_id: 0,
}
}
fn new_addr(hostname: *const libc::c_char, addr: Addr) -> Self {
match addr {
Addr::V4(ipv4) => Self::new_v4(hostname, ipv4),
Addr::V6 { ip, scope_id } => Self::new_v6(hostname, ip, scope_id),
}
}
fn new_v4(hostname: *const libc::c_char, ipv4: Ipv4Addr) -> Self {
let mut pat = Self::new(hostname);
pat.family = libc::AF_INET;
pat.addr[0] = u32::from_ne_bytes(ipv4.octets());
pat
}
fn new_v6(hostname: *const libc::c_char, ipv6: Ipv6Addr, scope_id: u32) -> Self {
let mut pat = Self::new(hostname);
pat.family = libc::AF_INET6;
pat.scope_id = scope_id;
ipv6.octets()
.chunks_exact(4)
.map(|bits| <[_; 4]>::try_from(bits).expect("exact chunk size is four"))
.map(u32::from_ne_bytes)
.zip(&mut pat.addr)
.for_each(|(val, slot)| *slot = val);
pat
}
}
#[cfg(test)]
mod conversion_tests {
use core::net::Ipv4Addr;
use core::net::Ipv6Addr;
use crate::GaihAddrTuple;
#[test]
fn ipv4_addr_is_network_byte_order() {
let t = GaihAddrTuple::new_v4(core::ptr::null(), Ipv4Addr::LOCALHOST);
let bytes: [u8; 16] = unsafe {
core::mem::transmute(t.addr)
};
assert_eq!(bytes[..4], Ipv4Addr::LOCALHOST.octets());
}
#[test]
fn ipv6_addr_is_network_byte_order() {
let t = GaihAddrTuple::new_v6(core::ptr::null(), Ipv6Addr::LOCALHOST, 0);
let bytes: [u8; 16] = unsafe {
core::mem::transmute(t.addr)
};
assert_eq!(bytes, Ipv6Addr::LOCALHOST.octets());
}
}