Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ impl<T> ComponentResult<T> {
error = &err as &dyn std::error::Error,
"error reported by {component}, ignoring...",
);
err.source();
ComponentResult::Err {
inner: ComponentError {
message: err.to_string(),
Expand Down
23 changes: 19 additions & 4 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,28 @@ async fn main() {
if !next_run_sleep.is_zero() {
tracing::info!(?next_run, "scheduling next run...");
}
std::thread::sleep(next_run_sleep);
tokio::time::sleep(next_run_sleep).await;

let system_information = SystemInformation::collect(&mut collect_ctx).await;

let serialized = serde_json::to_string_pretty(&system_information).unwrap();
if let Some(output_path) = &opts.output {
std::fs::write(output_path, &serialized).unwrap();
match serde_json::to_string_pretty(&system_information) {
Ok(serialized) => {
if let Some(output_path) = &opts.output
&& let Err(err) = std::fs::write(output_path, &serialized)
{
tracing::error!(
path = %output_path.display(),
error = &err as &dyn std::error::Error,
"failed to write JSON output file"
);
}
}
Err(err) => {
tracing::error!(
error = &err as &dyn std::error::Error,
"failed to serialize system information"
);
}
}

match opts.loop_interval {
Expand Down
4 changes: 2 additions & 2 deletions src/system_information/disk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ impl Disk {
#[tracing::instrument(name = "Disk::collect_all")]
pub fn collect_all() -> Vec<Self> {
let disks = sysinfo::Disks::new_with_refreshed_list();
if disks.into_iter().next().is_none() {
if disks.list().is_empty() {
tracing::info!("no disks found");
}
disks.into_iter().map(Self::from).collect()
disks.list().iter().map(Self::from).collect()
}
}

Expand Down
7 changes: 5 additions & 2 deletions src/system_information/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pub struct SystemInformation {
pub os: Option<os::OperatingSystem>,
pub current_user: Option<ComponentResult<user::User>>,
pub disks: Option<Vec<disk::Disk>>,
pub network: Option<network::SystemNetworkInfo>,
pub network: Option<ComponentResult<network::SystemNetworkInfo>>,
// TODO:
// Current time
// SElinux/AppArmor
Expand Down Expand Up @@ -70,7 +70,10 @@ impl SystemInformation {
user::User::collect_current(&ctx.system),
)),
disks: Some(disk::Disk::collect_all()),
network: Some(network::SystemNetworkInfo::collect().await),
network: Some(ComponentResult::report_from_result(
"SystemNetworkInfo::collect",
network::SystemNetworkInfo::collect().await,
)),
// ..Default::default()
};

Expand Down
111 changes: 60 additions & 51 deletions src/system_information/network.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,73 +3,86 @@ use hickory_resolver::{
};
use local_ip_address::list_afinet_netifas;
use serde::Serialize;
use snafu::{ResultExt, Snafu};
use std::{
collections::{BTreeSet, HashMap},
collections::{BTreeMap, BTreeSet},
net::IpAddr,
sync::LazyLock,
time::Duration,
};
use tokio::task::JoinSet;

static GLOBAL_DNS_RESOLVER: LazyLock<TokioResolver> = LazyLock::new(|| {
let (resolver_config, mut resolver_opts) =
read_system_conf().expect("failed to read system resolv config");
#[derive(Debug, Snafu)]
pub enum Error {
#[snafu(display("failed to list network interfaces"))]
ListInterfaces { source: local_ip_address::Error },
}

static GLOBAL_DNS_RESOLVER: LazyLock<Option<TokioResolver>> = LazyLock::new(|| {
let (resolver_config, mut resolver_opts) = match read_system_conf() {
Ok(conf) => conf,
Err(err) => {
tracing::error!(
error = &err as &dyn std::error::Error,
"failed to read system DNS config, DNS lookups will be skipped"
);
return None;
}
};
resolver_opts.timeout = Duration::from_secs(5);

TokioResolver::builder_with_config(resolver_config, TokioConnectionProvider::default())
.with_options(resolver_opts)
.build()
Some(
TokioResolver::builder_with_config(resolver_config, TokioConnectionProvider::default())
.with_options(resolver_opts)
.build(),
)
});

/// Captures all system network information, including network interfaces,
/// and the results of reverse and forward DNS lookups.
#[derive(Debug, Serialize)]
pub struct SystemNetworkInfo {
pub interfaces: HashMap<String, Vec<IpAddr>>,
pub reverse_lookups: HashMap<IpAddr, Vec<String>>,
pub forward_lookups: HashMap<String, Vec<IpAddr>>,
pub interfaces: BTreeMap<String, Vec<IpAddr>>,
pub reverse_lookups: BTreeMap<IpAddr, Vec<String>>,
pub forward_lookups: BTreeMap<String, Vec<IpAddr>>,
}

impl SystemNetworkInfo {
#[tracing::instrument(name = "SystemNetworkInfo::collect")]
pub async fn collect() -> SystemNetworkInfo {
let interfaces = match list_afinet_netifas() {
Ok(netifs) => {
let mut interface_map = std::collections::HashMap::new();
pub async fn collect() -> Result<SystemNetworkInfo, Error> {
let netifs = list_afinet_netifas().context(ListInterfacesSnafu)?;
let mut interfaces = BTreeMap::new();

// Iterate over the network interfaces and group them by name
// An interface may appear multiple times if it has multiple IP addresses (e.g. IPv4 and IPv6)
for (name, ip_addr) in netifs {
tracing::info!(
network.interface.name = name,
network.interface.address = %ip_addr,
"found network interface"
);
interface_map
.entry(name)
.or_insert_with(Vec::new)
.push(ip_addr);
}
interface_map
}
Err(error) => {
tracing::error!(
error = &error as &dyn std::error::Error,
"failed to list network interfaces"
);
HashMap::new()
}
};
// Iterate over the network interfaces and group them by name
// An interface may appear multiple times if it has multiple IP addresses (e.g. IPv4 and IPv6)
for (name, ip_addr) in netifs {
tracing::info!(
network.interface.name = name,
network.interface.address = %ip_addr,
"found network interface"
);
interfaces
.entry(name)
.or_insert_with(Vec::new)
.push(ip_addr);
}

let ips: BTreeSet<IpAddr> = interfaces.values().flatten().copied().collect();
tracing::info!(network.addresses.ip = ?ips, "ip addresses");

let mut reverse_lookups = JoinSet::new();
let Some(resolver) = GLOBAL_DNS_RESOLVER.as_ref() else {
return Ok(SystemNetworkInfo {
interfaces,
reverse_lookups: BTreeMap::new(),
forward_lookups: BTreeMap::new(),
});
};

let mut reverse_lookup_tasks = JoinSet::new();
for ip in ips {
reverse_lookups
.spawn(async move { (ip, GLOBAL_DNS_RESOLVER.reverse_lookup(ip).await) });
reverse_lookup_tasks.spawn(async move { (ip, resolver.reverse_lookup(ip).await) });
}
let reverse_lookups: HashMap<IpAddr, Vec<String>> = reverse_lookups
let reverse_lookups: BTreeMap<IpAddr, Vec<String>> = reverse_lookup_tasks
.join_all()
.await
.into_iter()
Expand All @@ -96,16 +109,12 @@ impl SystemNetworkInfo {
let hostname_set: BTreeSet<String> = reverse_lookups.values().flatten().cloned().collect();
tracing::info!(network.addresses.hostname = ?hostname_set, "hostnames");

let mut forward_lookups = JoinSet::new();
let mut forward_lookup_tasks = JoinSet::new();
for hostname in hostname_set {
forward_lookups.spawn(async move {
(
hostname.clone(),
GLOBAL_DNS_RESOLVER.lookup_ip(hostname).await,
)
});
forward_lookup_tasks
.spawn(async move { (hostname.clone(), resolver.lookup_ip(hostname).await) });
}
let forward_lookups: HashMap<String, Vec<IpAddr>> = forward_lookups
let forward_lookups: BTreeMap<String, Vec<IpAddr>> = forward_lookup_tasks
.join_all()
.await
.into_iter()
Expand All @@ -126,10 +135,10 @@ impl SystemNetworkInfo {
})
.collect();

SystemNetworkInfo {
Ok(SystemNetworkInfo {
interfaces,
reverse_lookups,
forward_lookups,
}
})
}
}
4 changes: 2 additions & 2 deletions src/system_information/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::error::SysinfoError;
pub enum Error {
#[snafu(display("failed to get pid of the current process"))]
GetCurrentPid { source: SysinfoError },
#[snafu(display("current pid {pid} could not be resolved to a proess"))]
#[snafu(display("current pid {pid} could not be resolved to a process"))]
ResolveCurrentProcess { pid: Pid },
}
type Result<T, E = Error> = std::result::Result<T, E>;
Expand Down Expand Up @@ -53,7 +53,7 @@ impl User {
tracing::info!(
user.name,
user.uid = user.uid.as_ref().map(|uid| format!("{uid:?}")),
user.gid = user.uid.as_ref().map(|gid| format!("{gid:?}")),
user.gid = user.gid.as_ref().map(|gid| format!("{gid:?}")),
"current user"
);
Ok(user)
Expand Down
Loading