mod types;
use std::{collections::HashMap, net::Ipv4Addr};
use tracing::{debug, warn};
pub(crate) use types::Ip4ConfigParams;
use wayle_core::{Property, unwrap_dbus};
use wayle_traits::Static;
use zbus::{
Connection,
zvariant::{OwnedObjectPath, OwnedValue},
};
use crate::{error::Error, proxy::ip4_config::IP4ConfigProxy};
#[derive(Debug, Clone)]
pub struct Ip4Config {
pub object_path: Property<OwnedObjectPath>,
pub address_data: Property<Vec<Ipv4Address>>,
pub gateway: Property<Option<Ipv4Addr>>,
pub nameserver_data: Property<Vec<Ipv4Addr>>,
pub domains: Property<Vec<String>>,
pub searches: Property<Vec<String>>,
pub dns_options: Property<Vec<String>>,
pub dns_priority: Property<i32>,
pub route_data: Property<Vec<Ipv4Route>>,
pub wins_server_data: Property<Vec<Ipv4Addr>>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Ipv4Address {
pub address: Ipv4Addr,
pub prefix: u8,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Ipv4Route {
pub destination: Ipv4Addr,
pub prefix: u8,
pub next_hop: Option<Ipv4Addr>,
pub metric: Option<u32>,
}
struct Ip4ConfigProperties {
address_data: Vec<Ipv4Address>,
gateway: Option<Ipv4Addr>,
nameserver_data: Vec<Ipv4Addr>,
domains: Vec<String>,
searches: Vec<String>,
dns_options: Vec<String>,
dns_priority: i32,
route_data: Vec<Ipv4Route>,
wins_server_data: Vec<Ipv4Addr>,
}
impl Static for Ip4Config {
type Error = Error;
type Context<'a> = Ip4ConfigParams<'a>;
async fn get(params: Self::Context<'_>) -> Result<Self, Self::Error> {
Self::from_path(params.connection, params.path).await
}
}
impl Ip4Config {
pub(crate) async fn resolve_address(
connection: &Connection,
path: OwnedObjectPath,
) -> Option<String> {
if path.as_str() == "/" {
return None;
}
match Self::get(Ip4ConfigParams {
connection,
path: path.clone(),
})
.await
{
Ok(config) => config
.address_data
.get()
.first()
.map(|addr| addr.address.to_string()),
Err(err) => {
warn!(error = %err, path = %path, "ip4 config fetch failed");
None
}
}
}
async fn from_path(connection: &Connection, path: OwnedObjectPath) -> Result<Self, Error> {
let properties = Self::fetch_properties(connection, &path).await?;
Ok(Self::from_props(path, properties))
}
async fn fetch_properties(
connection: &Connection,
path: &OwnedObjectPath,
) -> Result<Ip4ConfigProperties, Error> {
let proxy = IP4ConfigProxy::new(connection, path)
.await
.map_err(Error::DbusError)?;
let (
address_data,
gateway,
nameserver_data,
domains,
searches,
dns_options,
dns_priority,
route_data,
wins_server_data,
) = tokio::join!(
proxy.address_data(),
proxy.gateway(),
proxy.nameserver_data(),
proxy.domains(),
proxy.searches(),
proxy.dns_options(),
proxy.dns_priority(),
proxy.route_data(),
proxy.wins_server_data(),
);
Ok(Ip4ConfigProperties {
address_data: Self::parse_address_data(unwrap_dbus!(address_data, path)),
gateway: Self::parse_gateway(unwrap_dbus!(gateway, path)),
nameserver_data: Self::parse_nameserver_data(unwrap_dbus!(nameserver_data, path)),
domains: unwrap_dbus!(domains, path),
searches: unwrap_dbus!(searches, path),
dns_options: unwrap_dbus!(dns_options, path),
dns_priority: unwrap_dbus!(dns_priority, path),
route_data: Self::parse_route_data(unwrap_dbus!(route_data, path)),
wins_server_data: unwrap_dbus!(wins_server_data, path)
.into_iter()
.filter_map(|s| s.parse().ok())
.collect(),
})
}
fn from_props(path: OwnedObjectPath, props: Ip4ConfigProperties) -> Self {
Self {
object_path: Property::new(path),
address_data: Property::new(props.address_data),
gateway: Property::new(props.gateway),
nameserver_data: Property::new(props.nameserver_data),
domains: Property::new(props.domains),
searches: Property::new(props.searches),
dns_options: Property::new(props.dns_options),
dns_priority: Property::new(props.dns_priority),
route_data: Property::new(props.route_data),
wins_server_data: Property::new(props.wins_server_data),
}
}
fn parse_address_data(data: Vec<HashMap<String, OwnedValue>>) -> Vec<Ipv4Address> {
data.into_iter()
.filter_map(|entry| {
let address_value = entry.get("address")?;
let address_str = address_value.downcast_ref::<String>().ok()?;
let address = address_str.parse::<Ipv4Addr>().ok()?;
let prefix_value = entry.get("prefix")?;
let prefix_ref = prefix_value.downcast_ref::<&u32>().ok()?;
Some(Ipv4Address {
address,
prefix: *prefix_ref as u8,
})
})
.collect()
}
fn parse_gateway(gateway: String) -> Option<Ipv4Addr> {
match gateway.parse::<Ipv4Addr>() {
Ok(addr) if addr.is_unspecified() => None,
Ok(addr) => Some(addr),
Err(_) => None,
}
}
fn parse_nameserver_data(data: Vec<HashMap<String, OwnedValue>>) -> Vec<Ipv4Addr> {
data.into_iter()
.filter_map(|entry| {
let address_value = entry.get("address")?;
let address_str = address_value.downcast_ref::<String>().ok()?;
address_str.parse::<Ipv4Addr>().ok()
})
.collect()
}
fn parse_route_data(data: Vec<HashMap<String, OwnedValue>>) -> Vec<Ipv4Route> {
data.into_iter()
.filter_map(|entry| {
let dest_value = entry.get("dest")?;
let dest_str = dest_value.downcast_ref::<String>().ok()?;
let destination = match dest_str.parse::<Ipv4Addr>() {
Ok(addr) => addr,
Err(_) => {
debug!("Skipping route with invalid destination IP: {}", dest_str);
return None;
}
};
let prefix_value = entry.get("prefix")?;
let prefix_ref = prefix_value.downcast_ref::<&u8>().ok()?;
let next_hop = entry
.get("next-hop")
.and_then(|value| value.downcast_ref::<String>().ok())
.and_then(|addr_str| addr_str.parse::<Ipv4Addr>().ok())
.filter(|addr| !addr.is_unspecified());
let metric = entry
.get("metric")
.and_then(|value| value.downcast_ref::<&u32>().ok())
.copied();
Some(Ipv4Route {
destination,
prefix: *prefix_ref,
next_hop,
metric,
})
})
.collect()
}
}