Move to rust stable, introduce mock for dbus in tests
This commit is contained in:
parent
37ef5d4e65
commit
96663abaef
|
@ -15,7 +15,7 @@ FetchContent_Declare(
|
||||||
GIT_TAG v0.2.1
|
GIT_TAG v0.2.1
|
||||||
)
|
)
|
||||||
|
|
||||||
set(Rust_TOOLCHAIN nightly)
|
# set(Rust_TOOLCHAIN nightly)
|
||||||
FetchContent_MakeAvailable(Corrosion)
|
FetchContent_MakeAvailable(Corrosion)
|
||||||
corrosion_import_crate(MANIFEST_PATH Cargo.toml)
|
corrosion_import_crate(MANIFEST_PATH Cargo.toml)
|
||||||
|
|
||||||
|
|
34
Cargo.toml
34
Cargo.toml
|
@ -12,21 +12,23 @@ license = "GPL-3.0-or-later"
|
||||||
crate-type = ["cdylib"]
|
crate-type = ["cdylib"]
|
||||||
name = "nss_malcontent"
|
name = "nss_malcontent"
|
||||||
|
|
||||||
[dev-dependencies.rusty-hook]
|
[build-dependencies]
|
||||||
version = "0.11"
|
bindgen = "0.60"
|
||||||
|
|
||||||
[build-dependencies.bindgen]
|
[dev-dependencies]
|
||||||
version = "0.60"
|
futures-util = "0.3"
|
||||||
|
rusty-hook = "0.11"
|
||||||
|
test-cdylib = "1.1"
|
||||||
|
tokio = { version = "1", features = ["rt", "sync", "macros", "time"] }
|
||||||
|
zbus = { version = "3.0", default-features = false, features = ["tokio"] }
|
||||||
|
|
||||||
[dev-dependencies.test-cdylib]
|
[dependencies]
|
||||||
version = "1.1"
|
anyhow = "1.0"
|
||||||
|
const_format = "0.2"
|
||||||
[dependencies.anyhow]
|
libc = "0.2"
|
||||||
version = "1.0"
|
once_cell = "1.13"
|
||||||
|
gio = "0.15"
|
||||||
[dependencies.libc]
|
log = "0.4"
|
||||||
version = "0.2"
|
nix = { version = "0.25", features = ["socket", "user", "sched"] }
|
||||||
|
tokio = { version = "1", features = ["rt"] }
|
||||||
[dependencies.nix]
|
trust-dns-resolver = "0.21"
|
||||||
version = "0.25"
|
|
||||||
features = ["socket", "user", "sched"]
|
|
||||||
|
|
2
build.rs
2
build.rs
|
@ -1,8 +1,6 @@
|
||||||
// SPDX-FileCopyrightText: 2022 Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>
|
// SPDX-FileCopyrightText: 2022 Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
extern crate bindgen;
|
|
||||||
|
|
||||||
use {std::env, std::path::PathBuf};
|
use {std::env, std::path::PathBuf};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
use const_format::concatcp;
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
const DBUS_INTERFACE: &str = "com.endlessm.ParentalControls.Dns";
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
const DBUS_OBJECT_PATH: &str = "com/endlessm/ParentalControls/Dns";
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
const DBUS_GET_RESTRICTIONS_METHOD: &str = concatcp!(DBUS_INTERFACE, ".", "GetRestrictions");
|
|
@ -4,8 +4,8 @@
|
||||||
#![allow(non_upper_case_globals)]
|
#![allow(non_upper_case_globals)]
|
||||||
#![allow(non_camel_case_types)]
|
#![allow(non_camel_case_types)]
|
||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
#![feature(once_cell)]
|
|
||||||
|
|
||||||
|
mod constants;
|
||||||
pub mod nss_api;
|
pub mod nss_api;
|
||||||
mod policy_checker;
|
mod policy_checker;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
|
@ -31,17 +31,27 @@ pub unsafe extern "C" fn _nss_malcontent_gethostbyname4_r(
|
||||||
_ttlp: *mut i32,
|
_ttlp: *mut i32,
|
||||||
) -> nss_status {
|
) -> nss_status {
|
||||||
let name = CStr::from_ptr(name);
|
let name = CStr::from_ptr(name);
|
||||||
let policy_checker = MalcontentPolicyChecker::new(); // TODO: make LazySync
|
let policy_checker = MalcontentPolicyChecker::new(); // TODO: make LazySync, use RefCell for interior mutability
|
||||||
match policy_checker.restrictions(None) {
|
match policy_checker.resolver(None) {
|
||||||
Ok(None) => nss_status::NSS_STATUS_NOTFOUND,
|
Ok(None) => nss_status::NSS_STATUS_NOTFOUND,
|
||||||
Ok(Some(_addrs)) => {
|
Ok(Some(resolver)) => {
|
||||||
if name == CStr::from_bytes_with_nul_unchecked(CANARY_HOSTNAME) {
|
if name == CStr::from_bytes_with_nul_unchecked(CANARY_HOSTNAME) {
|
||||||
set_if_valid(errnop, errno::from_i32(0));
|
set_if_valid(errnop, errno::from_i32(0));
|
||||||
set_if_valid(h_errnop, HErrno::HostNotFound);
|
set_if_valid(h_errnop, HErrno::HostNotFound);
|
||||||
return nss_status::NSS_STATUS_SUCCESS;
|
return nss_status::NSS_STATUS_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
nss_status::NSS_STATUS_UNAVAIL
|
match resolver.lookup_ip(name.to_str().unwrap()) {
|
||||||
|
Ok(_result) => {
|
||||||
|
// TODO
|
||||||
|
// https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/posix/getaddrinfo.c;h=fed2d3bf130b6fc88e5c6840804bbadb8fe9a98b;hb=4ab2ab03d4351914ee53248dc5aef4a8c88ff8b9
|
||||||
|
// https://chromium.googlesource.com/chromiumos/third_party/glibc/+/cvs/fedora-glibc-2_8_90-1/nss/nss_files/files-hosts.c
|
||||||
|
// https://github.com/systemd/systemd/blob/d5548eb618d426a3952746eb3a0e7d95d186ea7e/src/nss-resolve/nss-resolve.c
|
||||||
|
|
||||||
|
nss_status::NSS_STATUS_SUCCESS
|
||||||
|
}
|
||||||
|
Err(_) => nss_status::NSS_STATUS_UNAVAIL,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Err(_err) => nss_status::NSS_STATUS_UNAVAIL,
|
Err(_err) => nss_status::NSS_STATUS_UNAVAIL,
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,17 +4,21 @@
|
||||||
use {
|
use {
|
||||||
anyhow::Result,
|
anyhow::Result,
|
||||||
nix::unistd::{getuid, Uid},
|
nix::unistd::{getuid, Uid},
|
||||||
std::net::{IpAddr, Ipv4Addr, Ipv6Addr},
|
once_cell::sync::Lazy,
|
||||||
std::sync::LazyLock,
|
std::collections::HashMap,
|
||||||
|
std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
|
||||||
|
std::sync::{Arc, RwLock},
|
||||||
|
trust_dns_resolver::config as dns_config,
|
||||||
|
trust_dns_resolver::Resolver,
|
||||||
};
|
};
|
||||||
|
|
||||||
type Restrictions<'a> = &'a [IpAddr];
|
pub type Restrictions<'a> = &'a [IpAddr];
|
||||||
|
|
||||||
pub trait PolicyChecker {
|
pub trait PolicyChecker {
|
||||||
fn restrictions<'a>(&'a self, user: Option<Uid>) -> Result<Option<Restrictions<'a>>>;
|
fn resolver(&self, user: Option<Uid>) -> Result<Option<Arc<Resolver>>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
static CLOUDFLARE_PARENTALCONTROL_ADDRS: LazyLock<Vec<IpAddr>> = LazyLock::new(|| {
|
static CLOUDFLARE_PARENTALCONTROL_ADDRS: Lazy<Vec<IpAddr>> = Lazy::new(|| {
|
||||||
vec![
|
vec![
|
||||||
IpAddr::V4(Ipv4Addr::new(1, 1, 1, 3)),
|
IpAddr::V4(Ipv4Addr::new(1, 1, 1, 3)),
|
||||||
IpAddr::V4(Ipv4Addr::new(1, 0, 0, 3)),
|
IpAddr::V4(Ipv4Addr::new(1, 0, 0, 3)),
|
||||||
|
@ -23,19 +27,21 @@ static CLOUDFLARE_PARENTALCONTROL_ADDRS: LazyLock<Vec<IpAddr>> = LazyLock::new(|
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
pub struct MalcontentPolicyChecker;
|
// TODO: make unique per process
|
||||||
|
// TODO: accept notifications about config changes
|
||||||
|
pub struct MalcontentPolicyChecker {
|
||||||
|
resolvers: RwLock<HashMap<Uid, Option<Arc<Resolver>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
impl MalcontentPolicyChecker {
|
impl MalcontentPolicyChecker {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {}
|
Self {
|
||||||
|
resolvers: RwLock::new(HashMap::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PolicyChecker for MalcontentPolicyChecker {
|
fn restrictions<'a>(&'a self, user: Uid) -> Result<Option<Restrictions<'a>>> {
|
||||||
fn restrictions<'a>(&'a self, user: Option<Uid>) -> Result<Option<Restrictions<'a>>> {
|
if user.is_root() {
|
||||||
let uid = user.unwrap_or_else(|| getuid());
|
|
||||||
|
|
||||||
if uid.is_root() {
|
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -45,3 +51,48 @@ impl PolicyChecker for MalcontentPolicyChecker {
|
||||||
Ok(Some(CLOUDFLARE_PARENTALCONTROL_ADDRS.as_slice()))
|
Ok(Some(CLOUDFLARE_PARENTALCONTROL_ADDRS.as_slice()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PolicyChecker for MalcontentPolicyChecker {
|
||||||
|
fn resolver(&self, user: Option<Uid>) -> Result<Option<Arc<Resolver>>> {
|
||||||
|
let user = user.unwrap_or_else(|| getuid());
|
||||||
|
{
|
||||||
|
let ro_resolvers = self.resolvers.read().unwrap();
|
||||||
|
if let Some(resolver) = ro_resolvers.get(&user) {
|
||||||
|
return Ok(resolver.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut rw_resolvers = self.resolvers.write().unwrap();
|
||||||
|
if rw_resolvers.contains_key(&user) {
|
||||||
|
return self.resolver(Some(user));
|
||||||
|
}
|
||||||
|
|
||||||
|
let resolver = match self.restrictions(user)? {
|
||||||
|
Some(addrs) => {
|
||||||
|
let resolver_config =
|
||||||
|
addrs
|
||||||
|
.iter()
|
||||||
|
.fold(dns_config::ResolverConfig::new(), |mut config, addr| {
|
||||||
|
config.add_name_server(dns_config::NameServerConfig {
|
||||||
|
socket_addr: SocketAddr::new(*addr, 53),
|
||||||
|
protocol: dns_config::Protocol::Udp,
|
||||||
|
tls_dns_name: None,
|
||||||
|
trust_nx_responses: true,
|
||||||
|
bind_addr: None,
|
||||||
|
});
|
||||||
|
config
|
||||||
|
});
|
||||||
|
|
||||||
|
let resolver =
|
||||||
|
Resolver::new(resolver_config, dns_config::ResolverOpts::default())?;
|
||||||
|
Some(Arc::new(resolver))
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
rw_resolvers.insert(user, resolver);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.resolver(Some(user))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -6,20 +6,25 @@
|
||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
|
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
|
||||||
|
include!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/constants.rs"));
|
||||||
|
|
||||||
use {
|
use {
|
||||||
anyhow::{anyhow, bail, ensure, Result},
|
anyhow::{anyhow, bail, ensure, Result},
|
||||||
|
futures_util::TryStreamExt,
|
||||||
libc::{freeaddrinfo, gai_strerror, getaddrinfo},
|
libc::{freeaddrinfo, gai_strerror, getaddrinfo},
|
||||||
nix::sys::socket::{SockaddrLike as _, SockaddrStorage},
|
nix::sys::socket::{SockaddrLike as _, SockaddrStorage},
|
||||||
|
nix::unistd::Uid,
|
||||||
|
std::collections::HashMap,
|
||||||
std::env,
|
std::env,
|
||||||
std::ffi::{CStr, CString},
|
std::ffi::{CStr, CString},
|
||||||
std::net::{IpAddr, Ipv4Addr, Ipv6Addr},
|
std::net::{IpAddr, Ipv4Addr, Ipv6Addr},
|
||||||
std::path::PathBuf,
|
std::path::PathBuf,
|
||||||
std::process::Command,
|
std::process::Command,
|
||||||
|
std::str::FromStr,
|
||||||
std::sync::Once,
|
std::sync::Once,
|
||||||
|
tokio::task,
|
||||||
|
tokio::task::JoinHandle,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub type Eai = EaiRetcode;
|
pub type Eai = EaiRetcode;
|
||||||
|
@ -106,3 +111,72 @@ pub fn resolve_system_and_us(hostname: &str) -> Result<(IpAddr, IpAddr)> {
|
||||||
Ok((ip_from_system, ip_from_us))
|
Ok((ip_from_system, ip_from_us))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(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
|
||||||
|
}
|
||||||
|
}?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
task::spawn(async { dbus_loop(responses).await })
|
||||||
|
}
|
||||||
|
|
|
@ -7,10 +7,25 @@ use {
|
||||||
crate::common::Eai,
|
crate::common::Eai,
|
||||||
anyhow::Result,
|
anyhow::Result,
|
||||||
libc::{freeaddrinfo, gai_strerror, getaddrinfo},
|
libc::{freeaddrinfo, gai_strerror, getaddrinfo},
|
||||||
std::net::{IpAddr, Ipv4Addr},
|
nix::unistd::getuid,
|
||||||
|
once_cell::sync::Lazy,
|
||||||
|
std::collections::HashMap,
|
||||||
|
std::net::{IpAddr, Ipv4Addr, Ipv6Addr},
|
||||||
|
std::time::Duration,
|
||||||
|
tokio::time::timeout,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static CLOUDFLARE_PARENTALCONTROL_ADDRS: Lazy<Vec<IpAddr>> = 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)),
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[ignore]
|
||||||
fn nss_module_is_loaded() -> Result<()> {
|
fn nss_module_is_loaded() -> Result<()> {
|
||||||
common::setup()?;
|
common::setup()?;
|
||||||
|
|
||||||
|
@ -37,10 +52,15 @@ fn nss_module_is_loaded() -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[tokio::test]
|
||||||
fn application_dns_is_nxdomain() -> Result<()> {
|
#[ignore]
|
||||||
common::setup()?;
|
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();
|
let hostname = std::ffi::CString::new("use-application-dns.net").unwrap();
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut addr = std::ptr::null_mut();
|
let mut addr = std::ptr::null_mut();
|
||||||
|
@ -60,32 +80,43 @@ fn application_dns_is_nxdomain() -> Result<()> {
|
||||||
);
|
);
|
||||||
freeaddrinfo(addr);
|
freeaddrinfo(addr);
|
||||||
};
|
};
|
||||||
Ok(())
|
|
||||||
|
timeout(Duration::from_secs(1), dbus).await??
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[tokio::test]
|
||||||
|
#[ignore]
|
||||||
|
async fn wikipedia_is_unrestricted() -> Result<()> {
|
||||||
|
let dbus = common::mock_dbus(HashMap::from([(
|
||||||
|
getuid(),
|
||||||
|
vec![CLOUDFLARE_PARENTALCONTROL_ADDRS.clone()],
|
||||||
|
)]));
|
||||||
|
|
||||||
fn wikipedia_is_unrestricted() -> Result<()> {
|
|
||||||
let (system_addr, our_addr) = common::resolve_system_and_us("wikipedia.org")?;
|
let (system_addr, our_addr) = common::resolve_system_and_us("wikipedia.org")?;
|
||||||
assert_eq!(system_addr, our_addr);
|
assert_eq!(system_addr, our_addr);
|
||||||
Ok(())
|
timeout(Duration::from_secs(1), dbus).await??
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[tokio::test]
|
||||||
#[ignore]
|
#[ignore]
|
||||||
fn adultsite_is_restricted() -> Result<()> {
|
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")?;
|
let (system_addr, our_addr) = common::resolve_system_and_us("pornhub.com")?;
|
||||||
assert_ne!(system_addr, our_addr);
|
assert_ne!(system_addr, our_addr);
|
||||||
assert_eq!(our_addr, IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)));
|
assert_eq!(our_addr, IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)));
|
||||||
Ok(())
|
timeout(Duration::from_secs(1), dbus).await??
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[tokio::test]
|
||||||
#[ignore]
|
#[ignore]
|
||||||
fn root_user_bypasses_restrictions() -> Result<()> {
|
async fn privileged_user_bypasses_restrictions() -> Result<()> {
|
||||||
// TODO fake root
|
let dbus = common::mock_dbus(HashMap::from([(getuid(), vec![ /* no restriction */])]));
|
||||||
|
|
||||||
let (system_addr, our_addr) = common::resolve_system_and_us("pornhub.com")?;
|
let (system_addr, our_addr) = common::resolve_system_and_us("pornhub.com")?;
|
||||||
assert_eq!(system_addr, our_addr);
|
assert_eq!(system_addr, our_addr);
|
||||||
Ok(())
|
timeout(Duration::from_secs(1), dbus).await??
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue