malcontent-dns-parental-con.../tests/integration_test.rs

196 lines
6.7 KiB
Rust

// SPDX-FileCopyrightText: 2022 Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>
// SPDX-License-Identifier: GPL-3.0-or-later
mod common;
use {
crate::common::{Eai, Restriction, Restrictions},
anyhow::Result,
libc::{freeaddrinfo, gai_strerror, getaddrinfo},
nix::unistd::getuid,
once_cell::sync::Lazy,
std::collections::HashMap,
std::net::{IpAddr, Ipv4Addr, Ipv6Addr},
};
static CLOUDFLARE_PARENTALCONTROL_ADDRS: Lazy<Restrictions> = Lazy::new(|| {
vec![
Restriction {
ip: IpAddr::V4(Ipv4Addr::new(1, 1, 1, 3)),
hostname: "family.cloudflare-dns.com".into(),
},
Restriction {
ip: IpAddr::V4(Ipv4Addr::new(1, 0, 0, 3)),
hostname: "family.cloudflare-dns.com".into(),
},
Restriction {
ip: IpAddr::V6(Ipv6Addr::new(0x2606, 0x4700, 0x4700, 0, 0, 0, 0, 0x1113)),
hostname: "family.cloudflare-dns.com".into(),
},
Restriction {
ip: IpAddr::V6(Ipv6Addr::new(0x2606, 0x4700, 0x4700, 0, 0, 0, 0, 0x1003)),
hostname: "family.cloudflare-dns.com".into(),
},
]
});
static NO_RESTRICTIONS: Lazy<Restrictions> = Lazy::new(|| vec![]);
fork_test! {
#[test]
fn nss_module_is_loaded() -> Result<()> {
common::setup()?;
tokio::runtime::Runtime::new().unwrap().block_on(async {
let _dbus = common::mock_dbus(HashMap::from([(getuid(), vec![NO_RESTRICTIONS.clone()])])).await?;
common::resolve_with_module(libc::AF_INET, "gnome.org")?;
Ok(())
})
}
#[test]
fn application_dns_is_nodata() -> Result<()> {
common::setup()?;
tokio::runtime::Runtime::new().unwrap().block_on(async {
let _dbus = common::mock_dbus(HashMap::from([(
getuid(),
vec![CLOUDFLARE_PARENTALCONTROL_ADDRS.clone()],
)])).await?;
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::NoData.0,
"Should have gotten no data (NODATA), instead got {}",
error.to_str().unwrap()
);
freeaddrinfo(addr);
};
Ok(())
})
}
#[test]
fn getaddrinfo_resolution() -> Result<()> {
common::setup()?;
const HOSTNAME: &str = "gnome.org";
tokio::runtime::Runtime::new().unwrap().block_on(async {
let _dbus = common::mock_dbus(HashMap::from([(
getuid(),
vec![CLOUDFLARE_PARENTALCONTROL_ADDRS.clone()],
)])).await?;
unsafe {
let mut addr = std::ptr::null_mut();
let getaddrinfo_status = getaddrinfo(
std::ffi::CString::new(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::Success.0,
"Should have gotten an hostname, instead got {}",
error.to_str().unwrap()
);
freeaddrinfo(addr);
};
Ok(())
})
}
#[test]
fn wikipedia_is_unrestricted() -> Result<()> {
common::setup()?;
const HOSTNAME: &str = "wikipedia.org";
tokio::runtime::Runtime::new().unwrap().block_on(async {
let _dbus = common::mock_dbus(HashMap::from([(
getuid(),
vec![CLOUDFLARE_PARENTALCONTROL_ADDRS.clone()],
)])).await?;
for family in [libc::AF_INET, libc::AF_INET6] {
let system_addr = common::resolve_with_system(family, HOSTNAME)?;
let our_addrs = common::resolve_with_module(family, HOSTNAME)?;
assert!(our_addrs.contains(&system_addr));
}
Ok(())
})
}
#[test]
fn adultsite_is_restricted_ipv4() -> Result<()> {
common::setup()?;
const HOSTNAME: &str = "nudity.testcategory.com";
tokio::runtime::Runtime::new().unwrap().block_on(async {
let _dbus = common::mock_dbus(HashMap::from([(
getuid(),
vec![CLOUDFLARE_PARENTALCONTROL_ADDRS.clone()],
)])).await?;
let system_addr = common::resolve_with_system(libc::AF_INET, HOSTNAME)?;
let our_addrs = common::resolve_with_module(libc::AF_INET, HOSTNAME)?;
assert!(!our_addrs.contains(&system_addr), "Resolver answered with {:?}, should not contain {}", our_addrs, system_addr);
assert_eq!(our_addrs, [IpAddr::V4(Ipv4Addr::UNSPECIFIED)]);
Ok(())
})
}
#[test]
fn adultsite_is_restricted_ipv6() -> Result<()> {
common::setup()?;
const HOSTNAME: &str = "nudity.testcategory.com";
tokio::runtime::Runtime::new().unwrap().block_on(async {
let _dbus = common::mock_dbus(HashMap::from([(
getuid(),
vec![CLOUDFLARE_PARENTALCONTROL_ADDRS.clone()],
)])).await;
let system_addr = common::resolve_with_system(libc::AF_INET6, HOSTNAME)?;
let our_addrs = common::resolve_with_module(libc::AF_INET6, HOSTNAME)?;
assert!(!our_addrs.contains(&system_addr), "Resolver answered with {:?}, should not contain {}", our_addrs, system_addr);
assert_eq!(our_addrs, [IpAddr::V6(Ipv6Addr::UNSPECIFIED)]);
Ok(())
})
}
#[test]
fn privileged_user_bypasses_restrictions() -> Result<()> {
common::setup()?;
const HOSTNAME: &str = "malware.testcategory.com";
tokio::runtime::Runtime::new().unwrap().block_on(async {
let _dbus = common::mock_dbus(HashMap::from([(getuid(), vec![NO_RESTRICTIONS.clone()])])).await?;
for family in [libc::AF_INET, libc::AF_INET6] {
let system_addr = common::resolve_with_system(family, HOSTNAME)?;
let our_addrs = common::resolve_with_module(family, HOSTNAME)?;
assert!(our_addrs.contains(&system_addr));
}
Ok(())
})
}
}