extern crate alloc;
use alloc::vec::IntoIter;
use core::ffi::CStr;
use core::mem::zeroed;
use core::ptr::{null, null_mut};
use core::str;
use core::str::FromStr;
use std::process::Command;
use errno::{errno, set_errno, Errno};
use libc::{c_char, c_int, size_t};
use rustix::net::{
addr::SocketAddrArg, addr::SocketAddrStorage, IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr,
SocketAddrV4, SocketAddrV6,
};
const EAI_ADDRFAMILY: c_int = -9;
#[no_mangle]
unsafe extern "C" fn getaddrinfo(
node: *const c_char,
service: *const c_char,
hints: *const libc::addrinfo,
res: *mut *mut libc::addrinfo,
) -> c_int {
libc!(libc::getaddrinfo(node, service, hints, res));
if node.is_null() && service.is_null() {
set_errno(Errno(libc::EINVAL));
return libc::EAI_NONAME;
}
let mut prototype: libc::addrinfo = zeroed();
if !hints.is_null() {
prototype = *hints;
if prototype.ai_flags & libc::AI_CANONNAME == libc::AI_CANONNAME {
if node.is_null() {
return libc::EAI_BADFLAGS;
}
prototype.ai_canonname = node.cast_mut();
}
assert_eq!(
prototype.ai_flags & !(libc::AI_NUMERICHOST | libc::AI_NUMERICSERV | libc::AI_CANONNAME | libc::AI_PASSIVE),
0,
"GAI flags hint other than AI_NUMERICHOST|AI_NUMERICSERV|AI_CANONNAME|AI_PASSIVE not supported yet"
);
assert_eq!(
prototype.ai_addrlen, 0,
"GAI addrlen hint not supported yet"
);
assert!(
prototype.ai_addr.is_null(),
"GAI addr hint not supported yet"
);
assert!(
prototype.ai_next.is_null(),
"GAI next hint not supported yet"
);
}
if prototype.ai_family == 0 {
prototype.ai_family = libc::AF_UNSPEC;
}
if prototype.ai_protocol == 0 {
prototype.ai_protocol = match prototype.ai_socktype {
0 => 0,
libc::SOCK_STREAM => libc::IPPROTO_TCP,
libc::SOCK_DGRAM => libc::IPPROTO_UDP,
_ => todo!("unimplemented GAI protocol {}", prototype.ai_protocol),
};
}
let port = if service.is_null() {
0
} else {
match resolve_service(service, &mut prototype) {
Ok(port) => port,
Err(err) => return err,
}
};
let layout = alloc::alloc::Layout::new::<libc::addrinfo>();
let addr_layout = alloc::alloc::Layout::new::<SocketAddrStorage>();
let mut first: *mut libc::addrinfo = null_mut();
let mut prev: *mut libc::addrinfo = null_mut();
if node.is_null() {
let v6_v4 = [libc::AF_INET6, libc::AF_INET];
let one_family = [prototype.ai_family];
let ai_families = match prototype.ai_family {
libc::AF_UNSPEC => &v6_v4[..],
libc::AF_INET | libc::AF_INET6 => &one_family[..],
_ => {
set_errno(Errno(libc::EILSEQ));
return libc::EAI_SERVICE;
}
};
let stream_dgram = [libc::SOCK_STREAM, libc::SOCK_DGRAM];
let one_socktype = [prototype.ai_socktype];
let ai_socktypes = match prototype.ai_socktype {
0 => &stream_dgram[..],
libc::SOCK_STREAM | libc::SOCK_DGRAM => &one_socktype[..],
_ => {
set_errno(Errno(libc::EILSEQ));
return libc::EAI_SERVICE;
}
};
for ai_family in ai_families {
for ai_socktype in ai_socktypes {
let ptr = alloc::alloc::alloc(layout).cast::<libc::addrinfo>();
ptr.write(prototype);
let info = &mut *ptr;
info.ai_socktype = *ai_socktype;
info.ai_family = *ai_family;
let storage = alloc::alloc::alloc(addr_layout).cast::<SocketAddrStorage>();
let is_passive = prototype.ai_flags & libc::AI_PASSIVE == libc::AI_PASSIVE;
let len = match *ai_family {
libc::AF_INET => {
let addr = if is_passive {
Ipv4Addr::UNSPECIFIED
} else {
Ipv4Addr::LOCALHOST
};
SocketAddrV4::new(addr, port).write_sockaddr(storage)
}
libc::AF_INET6 => {
let addr = if is_passive {
Ipv6Addr::UNSPECIFIED
} else {
Ipv6Addr::LOCALHOST
};
SocketAddrV6::new(addr, port, 0, 0).write_sockaddr(storage)
}
_ => unreachable!(),
};
info.ai_addr = storage.cast();
info.ai_addrlen = len;
if !prev.is_null() {
(*prev).ai_next = ptr;
}
prev = ptr;
if first.is_null() {
first = ptr;
}
}
}
*res = first;
return 0;
}
let host = match CStr::from_ptr(node.cast()).to_str() {
Ok(host) => host,
Err(_) => {
set_errno(Errno(libc::EILSEQ));
return libc::EAI_SYSTEM;
}
};
if prototype.ai_flags & libc::AI_NUMERICHOST == libc::AI_NUMERICHOST {
let addr = match IpAddr::from_str(host) {
Ok(addr) => addr,
Err(_err) => {
set_errno(Errno(libc::EIO));
return libc::EAI_NONAME;
}
};
match addr {
IpAddr::V4(_) => {
if prototype.ai_family == libc::AF_UNSPEC {
prototype.ai_family = libc::AF_INET;
}
if prototype.ai_family != libc::AF_INET {
set_errno(Errno(libc::EIO));
return libc::EAI_NONAME;
}
}
IpAddr::V6(_) => {
if prototype.ai_family == libc::AF_UNSPEC {
prototype.ai_family = libc::AF_INET6;
}
if prototype.ai_family != libc::AF_INET6 {
set_errno(Errno(libc::EIO));
return libc::EAI_NONAME;
}
}
}
let ptr = alloc::alloc::alloc(layout).cast::<libc::addrinfo>();
ptr.write(prototype);
let info = &mut *ptr;
let storage = alloc::alloc::alloc(addr_layout).cast::<SocketAddrStorage>();
let len = SocketAddr::new(addr, port).write_sockaddr(storage);
info.ai_addr = storage.cast();
info.ai_addrlen = len;
*res = ptr;
return 0;
}
match resolve_host(host, &prototype) {
Ok(addrs) => {
for addr in addrs {
let ptr = alloc::alloc::alloc(layout).cast::<libc::addrinfo>();
ptr.write(prototype);
let info = &mut *ptr;
match addr {
IpAddr::V4(v4) => {
if prototype.ai_family == libc::AF_UNSPEC
|| prototype.ai_family == libc::AF_INET
{
let storage =
alloc::alloc::alloc(addr_layout).cast::<SocketAddrStorage>();
let len = SocketAddrV4::new(v4, port).write_sockaddr(storage);
info.ai_addr = storage.cast();
info.ai_addrlen = len;
info.ai_family = libc::AF_INET;
}
}
IpAddr::V6(v6) => {
if prototype.ai_family == libc::AF_UNSPEC
|| prototype.ai_family == libc::AF_INET6
{
let storage =
alloc::alloc::alloc(addr_layout).cast::<SocketAddrStorage>();
let len = SocketAddrV6::new(v6, port, 0, 0).write_sockaddr(storage);
info.ai_addr = storage.cast();
info.ai_addrlen = len.try_into().unwrap();
info.ai_family = libc::AF_INET6;
}
}
}
if !prev.is_null() {
(*prev).ai_next = ptr;
}
prev = ptr;
if first.is_null() {
first = ptr;
}
}
*res = first;
0
}
Err(err) => err,
}
}
fn resolve_host(host: &str, prototype: &libc::addrinfo) -> Result<IntoIter<IpAddr>, c_int> {
let mut command = Command::new("getent");
match prototype.ai_family {
libc::AF_UNSPEC => command.arg("ahosts"),
libc::AF_INET => command.arg("ahostsv4"),
libc::AF_INET6 => command.arg("ahostsv6"),
_ => {
set_errno(Errno(libc::EIO));
return Err(libc::EAI_SERVICE);
}
};
command.arg(host);
let output = match command.output() {
Ok(output) => output,
Err(_err) => {
set_errno(Errno(libc::EIO));
return Err(libc::EAI_SYSTEM);
}
};
match output.status.code() {
Some(0) => {}
Some(2) => {
if matches!(prototype.ai_family, libc::AF_INET | libc::AF_INET6) {
let mut command = Command::new("getent");
command.arg("ahosts");
command.arg(host);
if let Ok(output) = command.output() {
if output.status.code() == Some(0) {
return Err(EAI_ADDRFAMILY);
}
}
}
return Err(libc::EAI_NONAME);
}
Some(r) => panic!("unexpected exit status from `getent ahosts`: {}", r),
None => {
set_errno(Errno(libc::EIO));
return Err(libc::EAI_SYSTEM);
}
}
let stdout = match str::from_utf8(&output.stdout) {
Ok(stdout) => stdout,
Err(_err) => {
set_errno(Errno(libc::EIO));
return Err(libc::EAI_SYSTEM);
}
};
let mut hosts = Vec::new();
for line in stdout.lines() {
let mut parts = line.split_ascii_whitespace();
let addr = match parts.next() {
Some(addr) => addr,
None => {
set_errno(Errno(libc::EIO));
return Err(libc::EAI_SYSTEM);
}
};
let type_ = match parts.next() {
Some(type_) => type_,
None => {
set_errno(Errno(libc::EIO));
return Err(libc::EAI_SYSTEM);
}
};
if prototype.ai_socktype != 0 {
let socktype_name = match prototype.ai_socktype {
libc::SOCK_STREAM => "STREAM",
libc::SOCK_DGRAM => "DGRAM",
libc::SOCK_RAW => "RAW",
_ => panic!("unsupported ai_socktype {:?}", prototype.ai_socktype),
};
if type_ != socktype_name {
continue;
}
}
let addr = match IpAddr::from_str(addr) {
Ok(addr) => addr,
Err(_err) => {
set_errno(Errno(libc::EIO));
return Err(libc::EAI_SYSTEM);
}
};
hosts.push(addr);
}
Ok(hosts.into_iter())
}
unsafe fn resolve_service(
service: *const c_char,
prototype: &mut libc::addrinfo,
) -> Result<u16, c_int> {
extern "C" {
fn getservbyname_r(
name: *const c_char,
proto: *const c_char,
result_buf: *mut libc::servent,
buf: *mut c_char,
buflen: size_t,
result: *mut *mut libc::servent,
) -> c_int;
}
if prototype.ai_flags & libc::AI_NUMERICSERV == libc::AI_NUMERICSERV {
set_errno(Errno(0));
let mut endptr: *mut c_char = null_mut();
let number = libc::strtol(service, &mut endptr, 10);
if endptr != service.cast_mut() && errno().0 != 0 {
if let Ok(number) = u16::try_from(number) {
return Ok(number);
}
}
return Err(libc::EAI_NONAME);
}
let proto = match prototype.ai_protocol {
libc::IPPROTO_TCP => c"tcp".as_ptr(),
libc::IPPROTO_UDP => c"udp".as_ptr(),
_ => null(),
};
let mut servent: libc::servent = zeroed();
let mut result = null_mut();
match getservbyname_r(service, proto, &mut servent, null_mut(), 0, &mut result) {
0 => {}
libc::ENOENT => {
let service_str = match CStr::from_ptr(service).to_str() {
Ok(service_str) => service_str,
Err(_) => return Err(libc::EAI_SERVICE),
};
let port = match u16::from_str(service_str) {
Ok(port) => port,
Err(_) => return Err(libc::EAI_SERVICE),
};
servent.s_proto = proto.cast_mut();
servent.s_port = port.to_be().into();
}
_ => return Err(libc::EAI_SERVICE),
};
match prototype.ai_protocol {
libc::IPPROTO_TCP => assert_eq!(libc::strcmp(servent.s_proto, c"tcp".as_ptr()), 0),
libc::IPPROTO_UDP => assert_eq!(libc::strcmp(servent.s_proto, c"udp".as_ptr()), 0),
_ => {
if !servent.s_proto.is_null() {
if libc::strcmp(servent.s_proto, c"tcp".as_ptr()) == 0 {
prototype.ai_protocol = libc::IPPROTO_TCP;
if prototype.ai_socktype == 0 {
prototype.ai_socktype = libc::SOCK_STREAM;
}
} else if libc::strcmp(servent.s_proto, c"udp".as_ptr()) == 0 {
prototype.ai_protocol = libc::IPPROTO_UDP;
if prototype.ai_socktype == 0 {
prototype.ai_socktype = libc::SOCK_DGRAM;
}
} else {
unreachable!();
}
}
}
}
Ok(u16::from_be(servent.s_port as u16))
}
#[no_mangle]
unsafe extern "C" fn freeaddrinfo(mut res: *mut libc::addrinfo) {
libc!(libc::freeaddrinfo(res));
let layout = alloc::alloc::Layout::new::<libc::addrinfo>();
let addr_layout = alloc::alloc::Layout::new::<SocketAddrStorage>();
while !res.is_null() {
let addr = (*res).ai_addr;
if !addr.is_null() {
alloc::alloc::dealloc(addr.cast(), addr_layout);
}
let old = res;
res = (*res).ai_next;
alloc::alloc::dealloc(old.cast(), layout);
}
}
#[no_mangle]
unsafe extern "C" fn gai_strerror(errcode: c_int) -> *const c_char {
libc!(libc::gai_strerror(errcode));
match errcode {
0 => c"Success",
libc::EAI_NONAME => c"Name does not resolve",
libc::EAI_SYSTEM => c"System error",
libc::EAI_BADFLAGS => c"Invalid flags",
libc::EAI_SERVICE => c"Unrecognized service",
EAI_ADDRFAMILY => c"Hostname has no addresses in address family",
_ => panic!("unrecognized gai_strerror {:?}", errcode),
}
.as_ptr()
}
#[no_mangle]
unsafe extern "C" fn __res_init() -> c_int {
libc!(libc::res_init());
0
}