Add TLS support to resolver, implement DBus ifaces
This commit is contained in:
parent
b5797b12f1
commit
c52195dd8b
12 changed files with 308 additions and 221 deletions
77
tests/common/dbus.rs
Normal file
77
tests/common/dbus.rs
Normal file
|
@ -0,0 +1,77 @@
|
|||
// SPDX-FileCopyrightText: 2022 Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
include!(concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/src/policy_checker/dbus.rs"
|
||||
));
|
||||
|
||||
use {
|
||||
event_listener::{Event, EventListener},
|
||||
nix::unistd::Uid,
|
||||
std::collections::HashMap,
|
||||
zbus::dbus_interface,
|
||||
};
|
||||
|
||||
pub struct MalcontentDBusMock {
|
||||
responses: HashMap<Uid, Vec<Restrictions>>,
|
||||
invocations_left: usize,
|
||||
finished: Event,
|
||||
}
|
||||
|
||||
#[dbus_interface(name = "com.endlessm.ParentalControls.Dns")]
|
||||
impl MalcontentDBusMock {
|
||||
fn get_restrictions(&mut self, user_id: u32) -> Restrictions {
|
||||
let answers = self
|
||||
.responses
|
||||
.get_mut(&Uid::from_raw(user_id))
|
||||
.expect(&format!(
|
||||
"MockError: No mocked invocations available for user with id {}",
|
||||
user_id
|
||||
));
|
||||
let restrictions = answers.pop().expect(&format!(
|
||||
"MockError: DBus mock is saturated for user with id {}",
|
||||
user_id
|
||||
));
|
||||
self.invocations_left -= 1;
|
||||
if self.invocations_left == 0 {
|
||||
self.finished.notify(1);
|
||||
}
|
||||
restrictions
|
||||
}
|
||||
}
|
||||
|
||||
impl MalcontentDBusMock {
|
||||
pub fn new(mut responses: HashMap<Uid, Vec<Restrictions>>) -> Self {
|
||||
let responses_size: usize = responses.values().map(|v| v.len()).sum();
|
||||
for r in responses.values_mut() {
|
||||
r.reverse(); // we pop responses from the back, so...
|
||||
}
|
||||
|
||||
let ret = Self {
|
||||
responses,
|
||||
invocations_left: responses_size,
|
||||
finished: Event::new(),
|
||||
};
|
||||
|
||||
if ret.invocations_left == 0 {
|
||||
ret.finished.notify(1);
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
pub fn waiter(&self) -> EventListener {
|
||||
self.finished.listen()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for MalcontentDBusMock {
|
||||
fn drop(&mut self) {
|
||||
assert_eq!(
|
||||
self.invocations_left, 0,
|
||||
"MockError: During teardown, {} invocations are still left on the mock object",
|
||||
self.invocations_left
|
||||
);
|
||||
}
|
||||
}
|
|
@ -7,11 +7,12 @@
|
|||
#![allow(dead_code)]
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
|
||||
include!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/constants.rs"));
|
||||
|
||||
mod dbus;
|
||||
|
||||
use {
|
||||
self::dbus::MalcontentDBusMock,
|
||||
anyhow::{anyhow, bail, ensure, Result},
|
||||
futures_util::TryStreamExt,
|
||||
libc::{freeaddrinfo, gai_strerror, getaddrinfo},
|
||||
nix::sys::socket::{SockaddrLike as _, SockaddrStorage},
|
||||
nix::unistd::Uid,
|
||||
|
@ -27,6 +28,8 @@ use {
|
|||
tokio::task::JoinHandle,
|
||||
};
|
||||
|
||||
pub use self::dbus::{Restriction, Restrictions};
|
||||
|
||||
// Adapted from rusty_forkfork (which inherits it from rusty_fork)
|
||||
// to allow a custom pre-fork function
|
||||
#[macro_export]
|
||||
|
@ -179,69 +182,16 @@ fn convert_addrinfo(sa: &SockaddrStorage) -> Result<IpAddr> {
|
|||
}
|
||||
}
|
||||
|
||||
pub type Restrictions = Vec<IpAddr>;
|
||||
|
||||
pub fn mock_dbus(responses: HashMap<Uid, Vec<Restrictions>>) -> JoinHandle<Result<()>> {
|
||||
async fn dbus_loop(mut responses: HashMap<Uid, Vec<Restrictions>>) -> Result<()> {
|
||||
use zbus::MessageType::*;
|
||||
|
||||
let mut responses_size: usize = responses.values().map(|v| v.len()).sum();
|
||||
for r in responses.values_mut() {
|
||||
r.reverse(); // we pop responses from the back, so...
|
||||
}
|
||||
|
||||
let connection = zbus::ConnectionBuilder::session()?.build().await?;
|
||||
let mut stream = zbus::MessageStream::from(&connection);
|
||||
while responses_size > 0 {
|
||||
let msg = stream
|
||||
.try_next()
|
||||
.await?
|
||||
.ok_or(anyhow!("Unparseable DBus message"))?;
|
||||
|
||||
if msg.header()?.message_type()? == MethodCall
|
||||
&& msg
|
||||
.interface()
|
||||
.ok_or(anyhow!("Invoked method has no interface"))?
|
||||
== DBUS_INTERFACE
|
||||
&& msg
|
||||
.member()
|
||||
.ok_or(anyhow!("Invoked method has no member"))?
|
||||
== DBUS_GET_RESTRICTIONS_METHOD
|
||||
{
|
||||
let user_id: u32 = msg.body()?;
|
||||
match responses.get_mut(&Uid::from_raw(user_id)) {
|
||||
Some(answers) => match answers.pop() {
|
||||
Some(a) => {
|
||||
responses_size = responses_size - 1;
|
||||
let ips: Vec<String> = a.into_iter().map(|ip| ip.to_string()).collect();
|
||||
connection.reply(&msg, &ips).await
|
||||
}
|
||||
None => {
|
||||
connection
|
||||
.reply_error(
|
||||
&msg,
|
||||
"MockExhausted",
|
||||
&format!("DBus mock is saturated for user with id {}", user_id),
|
||||
)
|
||||
.await
|
||||
}
|
||||
},
|
||||
None => {
|
||||
connection
|
||||
.reply_error(
|
||||
&msg,
|
||||
"MockExhausted",
|
||||
&format!(
|
||||
"No mocked invocations available for user with id {}",
|
||||
user_id
|
||||
),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}?;
|
||||
}
|
||||
}
|
||||
async fn dbus_loop(responses: HashMap<Uid, Vec<Restrictions>>) -> Result<()> {
|
||||
let mock = MalcontentDBusMock::new(responses);
|
||||
let waiter = mock.waiter();
|
||||
let _connection = zbus::ConnectionBuilder::session()?
|
||||
.serve_at("/com/endlessm/ParentalControls/Dns", mock)?
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
waiter.wait();
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -4,23 +4,35 @@
|
|||
mod common;
|
||||
|
||||
use {
|
||||
crate::common::Eai,
|
||||
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},
|
||||
//std::time::Duration,
|
||||
//tokio::time::timeout,
|
||||
std::time::Duration,
|
||||
tokio::time::timeout,
|
||||
};
|
||||
|
||||
static CLOUDFLARE_PARENTALCONTROL_ADDRS: Lazy<Vec<IpAddr>> = Lazy::new(|| {
|
||||
static CLOUDFLARE_PARENTALCONTROL_ADDRS: Lazy<Restrictions> = Lazy::new(|| {
|
||||
vec![
|
||||
IpAddr::V4(Ipv4Addr::new(1, 1, 1, 3)),
|
||||
IpAddr::V4(Ipv4Addr::new(1, 0, 0, 3)),
|
||||
IpAddr::V6(Ipv6Addr::new(2606, 4700, 4700, 0, 0, 0, 0, 1113)),
|
||||
IpAddr::V6(Ipv6Addr::new(2606, 4700, 4700, 0, 0, 0, 0, 1003)),
|
||||
Restriction {
|
||||
ip: IpAddr::V4(Ipv4Addr::new(1, 1, 1, 3)),
|
||||
hostname: "cloudflare-dns.com".into(),
|
||||
},
|
||||
Restriction {
|
||||
ip: IpAddr::V4(Ipv4Addr::new(1, 0, 0, 3)),
|
||||
hostname: "cloudflare-dns.com".into(),
|
||||
},
|
||||
Restriction {
|
||||
ip: IpAddr::V6(Ipv6Addr::new(2606, 4700, 4700, 0, 0, 0, 0, 1113)),
|
||||
hostname: "cloudflare-dns.com".into(),
|
||||
},
|
||||
Restriction {
|
||||
ip: IpAddr::V6(Ipv6Addr::new(2606, 4700, 4700, 0, 0, 0, 0, 1003)),
|
||||
hostname: "cloudflare-dns.com".into(),
|
||||
},
|
||||
]
|
||||
});
|
||||
|
||||
|
@ -36,7 +48,7 @@ fork_test! {
|
|||
fn application_dns_is_nxdomain() -> Result<()> {
|
||||
common::setup()?;
|
||||
tokio::runtime::Runtime::new().unwrap().block_on(async {
|
||||
let _dbus = common::mock_dbus(HashMap::from([(
|
||||
let dbus = common::mock_dbus(HashMap::from([(
|
||||
getuid(),
|
||||
vec![CLOUDFLARE_PARENTALCONTROL_ADDRS.clone()],
|
||||
)]));
|
||||
|
@ -61,25 +73,26 @@ fork_test! {
|
|||
freeaddrinfo(addr);
|
||||
};
|
||||
|
||||
//timeout(Duration::from_secs(1), dbus).await??
|
||||
Ok(())
|
||||
timeout(Duration::from_secs(1), dbus).await??
|
||||
})
|
||||
}
|
||||
|
||||
#[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([(
|
||||
let dbus = common::mock_dbus(HashMap::from([(
|
||||
getuid(),
|
||||
vec![CLOUDFLARE_PARENTALCONTROL_ADDRS.clone()],
|
||||
)]));
|
||||
|
||||
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::ffi::CString::new(HOSTNAME)?.as_ptr(),
|
||||
std::ptr::null(),
|
||||
std::ptr::null(),
|
||||
&mut addr,
|
||||
|
@ -95,8 +108,7 @@ fork_test! {
|
|||
freeaddrinfo(addr);
|
||||
};
|
||||
|
||||
//timeout(Duration::from_secs(1), dbus).await??
|
||||
Ok(())
|
||||
timeout(Duration::from_secs(1), dbus).await??
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -105,48 +117,64 @@ fork_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()],
|
||||
)]));
|
||||
|
||||
const HOSTNAME: &str = "wikipedia.org";
|
||||
|
||||
for family in [libc::AF_INET, libc::AF_INET6] {
|
||||
let dbus = common::mock_dbus(HashMap::from([(
|
||||
getuid(),
|
||||
vec![CLOUDFLARE_PARENTALCONTROL_ADDRS.clone()],
|
||||
)]));
|
||||
|
||||
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??
|
||||
timeout(Duration::from_secs(1), dbus).await???;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn adultsite_is_restricted() -> Result<()> {
|
||||
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([(
|
||||
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));
|
||||
|
||||
timeout(Duration::from_secs(1), dbus).await??
|
||||
})
|
||||
}
|
||||
|
||||
#[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()],
|
||||
)]));
|
||||
|
||||
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??
|
||||
Ok(())
|
||||
timeout(Duration::from_secs(1), dbus).await??
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -155,20 +183,17 @@ fork_test! {
|
|||
fn privileged_user_bypasses_restrictions() -> 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![ /* no restriction */])]));
|
||||
|
||||
const HOSTNAME: &str = "pornhub.com";
|
||||
|
||||
for family in [libc::AF_INET, libc::AF_INET6] {
|
||||
let dbus = common::mock_dbus(HashMap::from([(getuid(), vec![ /* no restriction */])]));
|
||||
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???
|
||||
}
|
||||
|
||||
//timeout(Duration::from_secs(1), dbus).await??
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue