Use an async context for module and refactor
This commit is contained in:
parent
96663abaef
commit
516bf4000c
12 changed files with 542 additions and 213 deletions
155
tests/common.rs
155
tests/common.rs
|
@ -27,38 +27,98 @@ use {
|
|||
tokio::task::JoinHandle,
|
||||
};
|
||||
|
||||
// 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<()> {
|
||||
let out_dir = PathBuf::from(env!("OUT_DIR"));
|
||||
let nss_config_status = unsafe {
|
||||
SETUP.call_once(|| {
|
||||
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
|
||||
SETUP.call_once(|| {
|
||||
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())
|
||||
};
|
||||
|
||||
let dest = out_dir.join(library_filename);
|
||||
std::fs::copy(library_path, dest).unwrap();
|
||||
});
|
||||
|
||||
let db = CString::new("hosts").unwrap();
|
||||
let resolvers = CString::new("malcontent dns").unwrap();
|
||||
__nss_configure_lookup(db.as_ptr(), resolvers.as_ptr())
|
||||
};
|
||||
|
||||
ensure!(
|
||||
nss_config_status == 0,
|
||||
"Unable to configure NSS to load module: __nss_configure_lookup() returned {}",
|
||||
nss_config_status
|
||||
);
|
||||
if nss_config_status != 0 {
|
||||
panic!(
|
||||
"Unable to configure NSS to load module: __nss_configure_lookup() returned {}",
|
||||
nss_config_status
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn system_resolve(host: &str) -> Result<IpAddr> {
|
||||
let process = Command::new("getent").arg("hosts").arg(host).output()?;
|
||||
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"
|
||||
|
@ -72,29 +132,26 @@ pub fn system_resolve(host: &str) -> Result<IpAddr> {
|
|||
Ok(IpAddr::from_str(&addr_string)?)
|
||||
}
|
||||
|
||||
pub fn convert_addrinfo(sa: &SockaddrStorage) -> Result<IpAddr> {
|
||||
if let Some(addr) = sa.as_sockaddr_in() {
|
||||
Ok(IpAddr::V4(Ipv4Addr::from(addr.ip())))
|
||||
} else if let Some(addr) = sa.as_sockaddr_in6() {
|
||||
Ok(IpAddr::V6(Ipv6Addr::from(addr.ip())))
|
||||
} else {
|
||||
bail!("addrinfo is not either an IPv4 or IPv6 address")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve_system_and_us(hostname: &str) -> Result<(IpAddr, IpAddr)> {
|
||||
setup()?;
|
||||
let ip_from_system = system_resolve(hostname)?;
|
||||
|
||||
pub fn resolve_with_module(family: libc::c_int, hostname: &str) -> Result<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 getaddrinfo_status = getaddrinfo(
|
||||
c_hostname.as_ptr(),
|
||||
std::ptr::null(),
|
||||
std::ptr::null(),
|
||||
&mut addr,
|
||||
);
|
||||
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!(
|
||||
|
@ -106,9 +163,19 @@ pub fn resolve_system_and_us(hostname: &str) -> Result<(IpAddr, IpAddr)> {
|
|||
);
|
||||
let addr_storage = SockaddrStorage::from_raw((*addr).ai_addr, Some((*addr).ai_addrlen))
|
||||
.ok_or(anyhow!("Garbled addrinfo from getaddrinfo()"))?;
|
||||
let ip_from_us = convert_addrinfo(&addr_storage)?;
|
||||
let ip = convert_addrinfo(&addr_storage)?;
|
||||
freeaddrinfo(addr);
|
||||
Ok((ip_from_system, ip_from_us))
|
||||
Ok(ip)
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_addrinfo(sa: &SockaddrStorage) -> Result<IpAddr> {
|
||||
if let Some(addr) = sa.as_sockaddr_in() {
|
||||
Ok(IpAddr::V4(Ipv4Addr::from(addr.ip())))
|
||||
} else if let Some(addr) = sa.as_sockaddr_in6() {
|
||||
Ok(IpAddr::V6(Ipv6Addr::from(addr.ip())))
|
||||
} else {
|
||||
bail!("addrinfo is not either an IPv4 or IPv6 address")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -24,99 +24,111 @@ static CLOUDFLARE_PARENTALCONTROL_ADDRS: Lazy<Vec<IpAddr>> = Lazy::new(|| {
|
|||
]
|
||||
});
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn nss_module_is_loaded() -> Result<()> {
|
||||
common::setup()?;
|
||||
fork_test! {
|
||||
#[test]
|
||||
fn nss_module_is_loaded() -> Result<()> {
|
||||
common::setup()?;
|
||||
common::resolve_with_module(libc::AF_INET, "gnome.org")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
let hostname = std::ffi::CString::new("gnome.org").unwrap();
|
||||
unsafe {
|
||||
let mut addr = std::ptr::null_mut();
|
||||
let getaddrinfo_status = getaddrinfo(
|
||||
hostname.as_ptr(),
|
||||
std::ptr::null(),
|
||||
std::ptr::null(),
|
||||
&mut addr,
|
||||
);
|
||||
#[test]
|
||||
fn application_dns_is_nxdomain() -> Result<()> {
|
||||
common::setup()?;
|
||||
tokio::runtime::Runtime::new().unwrap().block_on(async {
|
||||
let dbus = common::mock_dbus(HashMap::from([(
|
||||
getuid(),
|
||||
vec![CLOUDFLARE_PARENTALCONTROL_ADDRS.clone()],
|
||||
)]));
|
||||
|
||||
let error = std::ffi::CStr::from_ptr(gai_strerror(getaddrinfo_status));
|
||||
assert_eq!(
|
||||
getaddrinfo_status,
|
||||
0,
|
||||
"Unable to resolve hostname, getaddrinfo failed: {}",
|
||||
error.to_str().unwrap()
|
||||
);
|
||||
freeaddrinfo(addr);
|
||||
};
|
||||
let hostname = std::ffi::CString::new("use-application-dns.net").unwrap();
|
||||
unsafe {
|
||||
let mut addr = std::ptr::null_mut();
|
||||
let getaddrinfo_status = getaddrinfo(
|
||||
hostname.as_ptr(),
|
||||
std::ptr::null(),
|
||||
std::ptr::null(),
|
||||
&mut addr,
|
||||
);
|
||||
|
||||
let error = std::ffi::CStr::from_ptr(gai_strerror(getaddrinfo_status));
|
||||
assert_eq!(
|
||||
getaddrinfo_status,
|
||||
Eai::NoName.0,
|
||||
"Should have gotten no hostname (NXDOMAIN), instead got {}",
|
||||
error.to_str().unwrap()
|
||||
);
|
||||
freeaddrinfo(addr);
|
||||
};
|
||||
|
||||
timeout(Duration::from_secs(1), dbus).await??
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wikipedia_is_unrestricted() -> Result<()> {
|
||||
common::setup()?;
|
||||
|
||||
tokio::runtime::Runtime::new().unwrap().block_on(async {
|
||||
let dbus = common::mock_dbus(HashMap::from([(
|
||||
getuid(),
|
||||
vec![CLOUDFLARE_PARENTALCONTROL_ADDRS.clone()],
|
||||
)]));
|
||||
|
||||
const HOSTNAME: &str = "wikipedia.org";
|
||||
|
||||
for family in [libc::AF_INET, libc::AF_INET6] {
|
||||
let system_addr = common::resolve_with_system(family, HOSTNAME)?;
|
||||
let our_addr = common::resolve_with_module(family, HOSTNAME)?;
|
||||
assert_eq!(system_addr, our_addr);
|
||||
}
|
||||
|
||||
timeout(Duration::from_secs(1), dbus).await??
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn adultsite_is_restricted() -> Result<()> {
|
||||
common::setup()?;
|
||||
|
||||
tokio::runtime::Runtime::new().unwrap().block_on(async {
|
||||
let dbus = common::mock_dbus(HashMap::from([(
|
||||
getuid(),
|
||||
vec![CLOUDFLARE_PARENTALCONTROL_ADDRS.clone()],
|
||||
)]));
|
||||
|
||||
const HOSTNAME: &str = "pornhub.com";
|
||||
let system_addr = common::resolve_with_system(libc::AF_INET, HOSTNAME)?;
|
||||
let our_addr = common::resolve_with_module(libc::AF_INET, HOSTNAME)?;
|
||||
assert_ne!(system_addr, our_addr);
|
||||
assert_eq!(our_addr, IpAddr::V4(Ipv4Addr::UNSPECIFIED));
|
||||
|
||||
let system_addr = common::resolve_with_system(libc::AF_INET6, HOSTNAME)?;
|
||||
let our_addr = common::resolve_with_module(libc::AF_INET6, HOSTNAME)?;
|
||||
assert_ne!(system_addr, our_addr);
|
||||
assert_eq!(our_addr, IpAddr::V6(Ipv6Addr::UNSPECIFIED));
|
||||
|
||||
timeout(Duration::from_secs(1), dbus).await??
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn privileged_user_bypasses_restrictions() -> Result<()> {
|
||||
common::setup()?;
|
||||
|
||||
tokio::runtime::Runtime::new().unwrap().block_on(async {
|
||||
let dbus = common::mock_dbus(HashMap::from([(getuid(), vec![ /* no restriction */])]));
|
||||
|
||||
const HOSTNAME: &str = "pornhub.com";
|
||||
|
||||
for family in [libc::AF_INET, libc::AF_INET6] {
|
||||
let system_addr = common::resolve_with_system(family, HOSTNAME)?;
|
||||
let our_addr = common::resolve_with_module(family, HOSTNAME)?;
|
||||
assert_eq!(system_addr, our_addr);
|
||||
}
|
||||
|
||||
timeout(Duration::from_secs(1), dbus).await??
|
||||
})
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn application_dns_is_nxdomain() -> Result<()> {
|
||||
let dbus = common::mock_dbus(HashMap::from([(
|
||||
getuid(),
|
||||
vec![CLOUDFLARE_PARENTALCONTROL_ADDRS.clone()],
|
||||
)]));
|
||||
|
||||
common::setup()?;
|
||||
let hostname = std::ffi::CString::new("use-application-dns.net").unwrap();
|
||||
unsafe {
|
||||
let mut addr = std::ptr::null_mut();
|
||||
let getaddrinfo_status = getaddrinfo(
|
||||
hostname.as_ptr(),
|
||||
std::ptr::null(),
|
||||
std::ptr::null(),
|
||||
&mut addr,
|
||||
);
|
||||
|
||||
let error = std::ffi::CStr::from_ptr(gai_strerror(getaddrinfo_status));
|
||||
assert_eq!(
|
||||
getaddrinfo_status,
|
||||
Eai::NoName.0,
|
||||
"Should have gotten no hostname (NXDOMAIN), instead got {}",
|
||||
error.to_str().unwrap()
|
||||
);
|
||||
freeaddrinfo(addr);
|
||||
};
|
||||
|
||||
timeout(Duration::from_secs(1), dbus).await??
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn wikipedia_is_unrestricted() -> Result<()> {
|
||||
let dbus = common::mock_dbus(HashMap::from([(
|
||||
getuid(),
|
||||
vec![CLOUDFLARE_PARENTALCONTROL_ADDRS.clone()],
|
||||
)]));
|
||||
|
||||
let (system_addr, our_addr) = common::resolve_system_and_us("wikipedia.org")?;
|
||||
assert_eq!(system_addr, our_addr);
|
||||
timeout(Duration::from_secs(1), dbus).await??
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn adultsite_is_restricted() -> Result<()> {
|
||||
let dbus = common::mock_dbus(HashMap::from([(
|
||||
getuid(),
|
||||
vec![CLOUDFLARE_PARENTALCONTROL_ADDRS.clone()],
|
||||
)]));
|
||||
|
||||
let (system_addr, our_addr) = common::resolve_system_and_us("pornhub.com")?;
|
||||
assert_ne!(system_addr, our_addr);
|
||||
assert_eq!(our_addr, IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)));
|
||||
timeout(Duration::from_secs(1), dbus).await??
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn privileged_user_bypasses_restrictions() -> Result<()> {
|
||||
let dbus = common::mock_dbus(HashMap::from([(getuid(), vec![ /* no restriction */])]));
|
||||
|
||||
let (system_addr, our_addr) = common::resolve_system_and_us("pornhub.com")?;
|
||||
assert_eq!(system_addr, our_addr);
|
||||
timeout(Duration::from_secs(1), dbus).await??
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue