malcontent-dns-parental-con.../tests/common/mod.rs

194 lines
5.8 KiB
Rust

// SPDX-FileCopyrightText: 2022 Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>
// SPDX-License-Identifier: GPL-3.0-or-later
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(dead_code)]
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
mod dbus;
use {
anyhow::{anyhow, bail, ensure, Result},
libc::{freeaddrinfo, gai_strerror, getaddrinfo},
nix::sys::socket::{SockaddrLike as _, SockaddrStorage},
std::env,
std::ffi::{CStr, CString},
std::net::{IpAddr, Ipv4Addr, Ipv6Addr},
std::path::PathBuf,
std::process::Command,
std::str::FromStr,
std::sync::Once,
};
pub use self::dbus::{mock_dbus, Restriction, Restrictions};
// Adapted from rusty_forkfork (which inherits it from rusty_fork)
// to allow a custom pre-fork function
#[macro_export]
macro_rules! fork_test {
(#![rusty_fork(timeout_ms = $timeout:expr)]
$(
$(#[$meta:meta])*
fn $test_name:ident() $(-> $ret:ty)? $body:block
)*) => { $(
$(#[$meta])*
fn $test_name() {
// Eagerly convert everything to function pointers so that all
// tests use the same instantiation of `fork`.
fn body_fn() $(-> $ret)? $body
let body: fn () $(-> $ret)? = body_fn;
fn supervise_fn(child: &mut rusty_forkfork::ChildWrapper,
_file: &mut ::std::fs::File) {
rusty_forkfork::fork_test::supervise_child(child, $timeout)
}
let supervise:
fn (&mut rusty_forkfork::ChildWrapper, &mut ::std::fs::File) =
supervise_fn;
rusty_forkfork::fork(
rusty_forkfork::rusty_fork_test_name!($test_name),
rusty_forkfork::rusty_fork_id!(),
$crate::common::prefork_setup,
supervise, body).expect("forking test failed");
}
)* };
($(
$(#[$meta:meta])*
fn $test_name:ident() $(-> $ret:ty)? $body:block
)*) => {
fork_test! {
#![rusty_fork(timeout_ms = 0)]
$($(#[$meta])* fn $test_name() $(-> $ret)? $body)*
}
};
}
pub fn prefork_setup(_child: &mut Command) {
let out_dir = PathBuf::from(env!("OUT_DIR"));
PREFORK.call_once(|| {
std::env::set_var("LD_LIBRARY_PATH", &out_dir);
let library_path = test_cdylib::build_current_project();
let mut library_filename = library_path.file_name().unwrap().to_owned();
library_filename.push(".2"); // required for NSS modules
let dest = out_dir.join(library_filename);
std::fs::copy(library_path, &dest).unwrap();
});
}
pub type Eai = EaiRetcode;
static PREFORK: Once = Once::new();
static SETUP: Once = Once::new();
pub fn setup() -> Result<()> {
SETUP.call_once(|| {
env_logger::init();
let nss_config_status = unsafe {
let db = CString::new("hosts").unwrap();
let resolvers = CString::new("malcontent dns").unwrap();
__nss_configure_lookup(db.as_ptr(), resolvers.as_ptr())
};
if nss_config_status != 0 {
panic!(
"Unable to configure NSS to load module: __nss_configure_lookup() returned {}",
nss_config_status
);
}
});
Ok(())
}
pub fn resolve_with_system(family: libc::c_int, host: &str) -> Result<IpAddr> {
let process = Command::new("getent")
.arg(match family {
libc::AF_INET => "ahostsv4",
libc::AF_INET6 => "ahostsv6",
_ => panic!("INET family must be either IPv4 or IPv6"),
})
.arg(host)
.output()?;
ensure!(
process.status.success(),
"Failed to run getent to check host IP"
);
let output = String::from_utf8(process.stdout)?;
let addr_string = output
.as_str()
.split(' ')
.next()
.ok_or(anyhow!("Unparseable output from getent"))?;
Ok(IpAddr::from_str(&addr_string)?)
}
pub fn resolve_with_module(family: libc::c_int, hostname: &str) -> Result<Vec<IpAddr>> {
assert!(
SETUP.is_completed(),
"Forgot to call common::setup() at beginning of test?"
);
let c_hostname = CString::new(hostname).unwrap();
unsafe {
let mut addr = std::ptr::null_mut();
let hints = libc::addrinfo {
ai_family: family,
ai_flags: 0,
ai_socktype: 0,
ai_protocol: 0,
ai_addrlen: 0,
ai_addr: std::ptr::null_mut(),
ai_canonname: std::ptr::null_mut(),
ai_next: std::ptr::null_mut(),
};
let getaddrinfo_status =
getaddrinfo(c_hostname.as_ptr(), std::ptr::null(), &hints, &mut addr);
let error = CStr::from_ptr(gai_strerror(getaddrinfo_status));
assert_eq!(
getaddrinfo_status,
Eai::Success.0,
"Should have gotten hostname for {}, instead got {}",
hostname,
error.to_str().unwrap()
);
let ips = convert_addrinfo(addr)?;
freeaddrinfo(addr);
Ok(ips)
}
}
unsafe fn convert_addrinfo(addrs: *const libc::addrinfo) -> Result<Vec<IpAddr>> {
let mut ips = vec![];
let mut addr = addrs;
while addr != std::ptr::null() {
let addr_storage = SockaddrStorage::from_raw((*addr).ai_addr, Some((*addr).ai_addrlen))
.ok_or(anyhow!("Garbled addrinfo from getaddrinfo()"))?;
if let Some(addr) = addr_storage.as_sockaddr_in() {
ips.push(IpAddr::V4(Ipv4Addr::from(addr.ip())));
} else if let Some(addr) = addr_storage.as_sockaddr_in6() {
ips.push(IpAddr::V6(Ipv6Addr::from(addr.ip())));
} else {
bail!("addrinfo is not either an IPv4 or IPv6 address")
}
addr = (*addr).ai_next;
}
ips.sort();
ips.dedup();
Ok(ips)
}