use std::{
convert::TryFrom,
str::FromStr,
time::{Duration, Instant},
};
use log::debug;
use super::{
NmIfaceType,
active_connection::{
NmActiveConnection, get_nm_ac_by_obj_path, nm_ac_obj_path_uuid_get,
},
connection::{NmConnection, nm_con_get_from_obj_path},
dbus::NmDbus,
device::{NmDevice, NmDeviceState, NmDeviceStateReason},
dns::{NmDnsEntry, NmGlobalDnsConfig},
error::{ErrorKind, NmError},
lldp::NmLldpNeighbor,
query_apply::device::{
nm_dev_delete, nm_dev_disconnect, nm_dev_from_obj_path, nm_dev_get_llpd,
},
};
pub struct NmApi<'a> {
pub(crate) dbus: NmDbus<'a>,
checkpoint: Option<String>,
cp_refresh_time: Option<std::time::Instant>,
cp_timeout: u32,
auto_cp_refresh: bool,
}
pub struct NmVersionInfo {
pub version_encoded: u32,
capabilities: Vec<u32>,
}
#[derive(Eq, PartialEq)]
pub struct NmVersion {
pub major: u8,
pub minor: u8,
pub micro: u8,
}
impl NmApi<'_> {
pub async fn new() -> Result<Self, NmError> {
Ok(Self {
dbus: NmDbus::new().await?,
checkpoint: None,
cp_refresh_time: None,
cp_timeout: 0,
auto_cp_refresh: false,
})
}
pub fn set_checkpoint(&mut self, checkpoint: &str, timeout: u32) {
self.checkpoint = Some(checkpoint.to_string());
self.cp_timeout = timeout;
self.cp_refresh_time = Some(std::time::Instant::now());
}
pub fn set_checkpoint_auto_refresh(&mut self, value: bool) {
self.auto_cp_refresh = value;
}
pub async fn version(&self) -> Result<NmVersion, NmError> {
self.dbus.version().await.and_then(|ver| ver.parse())
}
pub async fn version_info(&self) -> Result<NmVersionInfo, NmError> {
self.dbus
.version_info()
.await
.map(|version_info_arr| NmVersionInfo::from(&version_info_arr))
}
pub async fn checkpoint_create(
&mut self,
timeout: u32,
) -> Result<String, NmError> {
debug!("checkpoint_create");
let cp = self.dbus.checkpoint_create(timeout).await?;
debug!("checkpoint created: {}", &cp);
self.checkpoint = Some(cp.clone());
self.cp_refresh_time = Some(std::time::Instant::now());
self.cp_timeout = timeout;
Ok(cp)
}
pub async fn checkpoint_destroy(
&mut self,
checkpoint: &str,
) -> Result<(), NmError> {
let mut checkpoint_to_destroy: String = checkpoint.to_string();
if checkpoint_to_destroy.is_empty() {
checkpoint_to_destroy = self.last_active_checkpoint().await?
}
self.checkpoint = None;
self.cp_refresh_time = None;
debug!("checkpoint_destroy: {checkpoint_to_destroy}");
self.dbus
.checkpoint_destroy(checkpoint_to_destroy.as_str())
.await
}
pub async fn checkpoint_rollback(
&mut self,
checkpoint: &str,
) -> Result<(), NmError> {
let mut checkpoint_to_rollback: String = checkpoint.to_string();
if checkpoint_to_rollback.is_empty() {
checkpoint_to_rollback = self.last_active_checkpoint().await?
}
self.checkpoint = None;
self.cp_refresh_time = None;
debug!("checkpoint_rollback: {checkpoint_to_rollback}");
self.dbus
.checkpoint_rollback(checkpoint_to_rollback.as_str())
.await
}
async fn last_active_checkpoint(&self) -> Result<String, NmError> {
debug!("last_active_checkpoint");
let mut checkpoints = self.dbus.checkpoints().await?;
if !checkpoints.is_empty() {
Ok(checkpoints.remove(0))
} else {
Err(NmError::new(
ErrorKind::NotFound,
"Not active checkpoints".to_string(),
))
}
}
pub async fn connection_activate(
&mut self,
uuid: &str,
) -> Result<(), NmError> {
debug!("connection_activate: {uuid}");
self.extend_timeout_if_required().await?;
let nm_conn = self.dbus.get_conn_obj_path_by_uuid(uuid).await?;
self.dbus.connection_activate(&nm_conn).await
}
pub async fn connection_deactivate(
&mut self,
uuid: &str,
) -> Result<(), NmError> {
debug!("connection_deactivate: {uuid}");
self.extend_timeout_if_required().await?;
if let Ok(nm_ac) = get_nm_ac_obj_path_by_uuid(&self.dbus, uuid).await
&& !nm_ac.is_empty()
{
self.dbus.connection_deactivate(&nm_ac).await?;
}
Ok(())
}
pub async fn connections_get(
&mut self,
) -> Result<Vec<NmConnection>, NmError> {
debug!("connections_get");
self.extend_timeout_if_required().await?;
let mut nm_conns = Vec::new();
for nm_conn_obj_path in self.dbus.nm_conn_obj_paths_get().await? {
if let Ok(c) = nm_con_get_from_obj_path(
&self.dbus.connection,
&nm_conn_obj_path,
)
.await
{
nm_conns.push(c);
}
}
Ok(nm_conns)
}
pub async fn applied_connections_get(
&mut self,
) -> Result<Vec<NmConnection>, NmError> {
debug!("applied_connections_get");
self.extend_timeout_if_required().await?;
let nm_dev_obj_paths = self.dbus.nm_dev_obj_paths_get().await?;
let mut nm_conns: Vec<NmConnection> = Vec::new();
for nm_dev_obj_path in nm_dev_obj_paths {
self.extend_timeout_if_required().await?;
match self
.dbus
.nm_dev_applied_connection_get(&nm_dev_obj_path)
.await
{
Ok(mut nm_conn) => {
if nm_conn
.connection
.as_ref()
.map(|c| c.iface_name.is_none())
.unwrap_or_default()
&& let (Ok(nm_dev), Some(nm_set)) = (
nm_dev_from_obj_path(
&self.dbus.connection,
&nm_dev_obj_path,
)
.await,
nm_conn.connection.as_mut(),
)
{
nm_set.iface_name = Some(nm_dev.name.clone());
}
nm_conns.push(nm_conn)
}
Err(e) => {
debug!(
"Ignoring error when get applied connection for dev \
{nm_dev_obj_path}: {e}"
);
}
}
}
Ok(nm_conns)
}
pub async fn connection_add(
&mut self,
nm_conn: &NmConnection,
memory_only: bool,
) -> Result<(), NmError> {
debug!("connection_add: {nm_conn:?}");
self.extend_timeout_if_required().await?;
if !nm_conn.obj_path.is_empty() {
self.dbus
.connection_update(
nm_conn.obj_path.as_str(),
nm_conn,
memory_only,
)
.await
} else {
self.dbus.connection_add(nm_conn, memory_only).await
}
}
pub async fn connection_delete(
&mut self,
uuid: &str,
) -> Result<(), NmError> {
debug!("connection_delete: {uuid}");
self.extend_timeout_if_required().await?;
if let Ok(con_obj_path) =
self.dbus.get_conn_obj_path_by_uuid(uuid).await
{
debug!("Found nm_connection {con_obj_path} for UUID {uuid}");
if !con_obj_path.is_empty() {
self.dbus.connection_delete(&con_obj_path).await?;
}
}
Ok(())
}
pub async fn connection_reapply(
&mut self,
nm_conn: &NmConnection,
nm_dev_obj_path: &str,
) -> Result<(), NmError> {
debug!("connection_reapply: {nm_conn:?}");
self.extend_timeout_if_required().await?;
self.dbus.nm_dev_reapply(nm_dev_obj_path, nm_conn).await
}
pub async fn active_connections_get(
&mut self,
) -> Result<Vec<NmActiveConnection>, NmError> {
debug!("active_connections_get");
self.extend_timeout_if_required().await?;
let mut nm_acs = Vec::new();
let nm_ac_obj_paths = self.dbus.active_connections().await?;
for nm_ac_obj_path in nm_ac_obj_paths {
if let Ok(Some(nm_ac)) =
get_nm_ac_by_obj_path(&self.dbus.connection, &nm_ac_obj_path)
.await
{
debug!("Got active connection {nm_ac:?}");
nm_acs.push(nm_ac);
}
}
Ok(nm_acs)
}
pub async fn checkpoint_timeout_extend(
&self,
checkpoint: &str,
added_time_sec: u32,
) -> Result<(), NmError> {
debug!("checkpoint_timeout_extend: {checkpoint} {added_time_sec}");
self.dbus
.checkpoint_timeout_extend(checkpoint, added_time_sec)
.await
}
pub async fn devices_get(&mut self) -> Result<Vec<NmDevice>, NmError> {
debug!("devices_get");
self.extend_timeout_if_required().await?;
let mut ret = Vec::new();
for nm_dev_obj_path in &self.dbus.nm_dev_obj_paths_get().await? {
match nm_dev_from_obj_path(&self.dbus.connection, nm_dev_obj_path)
.await
{
Ok(nm_dev) => {
debug!("Got Device {nm_dev:?}");
ret.push(nm_dev);
}
Err(e) => {
debug!("Failed to retrieve device {nm_dev_obj_path} {e}")
}
}
}
Ok(ret)
}
pub async fn device_disconnect(
&mut self,
nm_dev_obj_path: &str,
) -> Result<(), NmError> {
self.extend_timeout_if_required().await?;
nm_dev_disconnect(&self.dbus.connection, nm_dev_obj_path).await
}
pub async fn device_delete(
&mut self,
nm_dev_obj_path: &str,
) -> Result<(), NmError> {
self.extend_timeout_if_required().await?;
nm_dev_delete(&self.dbus.connection, nm_dev_obj_path).await
}
pub async fn device_lldp_neighbor_get(
&mut self,
nm_dev_obj_path: &str,
) -> Result<Vec<NmLldpNeighbor>, NmError> {
self.extend_timeout_if_required().await?;
nm_dev_get_llpd(&self.dbus.connection, nm_dev_obj_path).await
}
pub async fn wait_checkpoint_rollback(
&mut self,
timeout: u32,
) -> Result<(), NmError> {
debug!("wait_checkpoint_rollback");
let start = Instant::now();
while start.elapsed() <= Duration::from_secs(timeout.into()) {
let mut waiting_nm_dev: Vec<&NmDevice> = Vec::new();
let nm_devs = self.devices_get().await?;
for nm_dev in &nm_devs {
if nm_dev.state_reason == NmDeviceStateReason::NewActivation
|| nm_dev.state == NmDeviceState::Deactivating
{
waiting_nm_dev.push(nm_dev);
}
}
if waiting_nm_dev.is_empty() {
return Ok(());
} else {
debug!("Waiting rollback on these devices {waiting_nm_dev:?}");
tokio::time::sleep(Duration::from_millis(500)).await;
}
}
Err(NmError::new(
ErrorKind::Timeout,
"Timeout on waiting rollback".to_string(),
))
}
pub async fn get_dns_configuration(
&mut self,
) -> Result<Vec<NmDnsEntry>, NmError> {
let mut ret: Vec<NmDnsEntry> = Vec::new();
self.extend_timeout_if_required().await?;
for dns_value in self.dbus.get_dns_configuration().await? {
ret.push(NmDnsEntry::try_from(dns_value)?);
}
Ok(ret)
}
pub async fn hostname_set(
&mut self,
hostname: &str,
) -> Result<(), NmError> {
self.extend_timeout_if_required().await?;
if hostname.is_empty() {
if std::path::Path::new("/etc/hostname").exists()
&& let Err(e) = std::fs::remove_file("/etc/hostname")
{
log::error!("Failed to remove static /etc/hostname: {e}");
}
Ok(())
} else {
self.dbus.hostname_set(hostname).await
}
}
pub async fn extend_timeout_if_required(&mut self) -> Result<(), NmError> {
if let (Some(cp_refresh_time), Some(checkpoint)) =
(self.cp_refresh_time.as_ref(), self.checkpoint.as_ref())
{
if self.auto_cp_refresh
&& cp_refresh_time.elapsed().as_secs()
>= self.cp_timeout as u64 / 2
{
log::debug!("Extending checkpoint timeout");
self.checkpoint_timeout_extend(checkpoint, self.cp_timeout)
.await?;
self.cp_refresh_time = Some(Instant::now());
}
}
Ok(())
}
pub async fn get_global_dns_configuration(
&self,
) -> Result<Option<NmGlobalDnsConfig>, NmError> {
NmGlobalDnsConfig::from_value(
self.dbus.global_dns_configuration().await?,
)
}
pub async fn set_global_dns_configuration(
&mut self,
config: &NmGlobalDnsConfig,
) -> Result<(), NmError> {
self.extend_timeout_if_required().await?;
self.dbus
.set_global_dns_configuration(config.to_value()?)
.await
}
}
impl NmVersionInfo {
pub const CAPABILITY_SYNC_ROUTE_WITH_TABLE: usize = 0;
pub(crate) const CAPABILITY_IP4_FORWARDING: usize = 1;
fn from(ver_info_arr: &[u32]) -> Self {
Self {
version_encoded: *ver_info_arr.first().unwrap_or(&0),
capabilities: ver_info_arr[1..].to_vec(),
}
}
pub fn version(&self) -> NmVersion {
NmVersion {
major: ((self.version_encoded >> 16) & 0xFF) as u8,
minor: ((self.version_encoded >> 8) & 0xFF) as u8,
micro: (self.version_encoded & 0xFF) as u8,
}
}
pub fn has_capability(&self, cap: usize) -> bool {
let idx = cap / 32;
let bit = cap % 32;
match self.capabilities.get(idx) {
Some(chunk) => (*chunk & (1 << bit)) != 0,
None => false,
}
}
}
impl NmVersion {
pub fn new(major: u8, minor: u8, micro: u8) -> NmVersion {
NmVersion {
major,
minor,
micro,
}
}
pub fn encoded(&self) -> u32 {
((self.major as u32) << 16)
| ((self.minor as u32) << 8)
| (self.micro as u32)
}
}
impl Ord for NmVersion {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.encoded().cmp(&other.encoded())
}
}
impl PartialOrd for NmVersion {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl FromStr for NmVersion {
type Err = NmError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Ok(ver) = s
.split('.')
.take(3)
.map(|v| v.parse::<u8>())
.collect::<Result<Vec<u8>, _>>()
&& ver.len() == 3
{
return Ok(NmVersion {
major: ver[0],
minor: ver[1],
micro: ver[2],
});
}
Err(NmError::new(
ErrorKind::InvalidArgument,
format!("Cannot parse version '{s}'"),
))
}
}
impl std::fmt::Display for NmVersion {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.micro)
}
}
async fn get_nm_ac_obj_path_by_uuid(
dbus: &NmDbus<'_>,
uuid: &str,
) -> Result<String, NmError> {
let nm_ac_obj_paths = dbus.active_connections().await?;
for nm_ac_obj_path in nm_ac_obj_paths {
if nm_ac_obj_path_uuid_get(&dbus.connection, &nm_ac_obj_path).await?
== uuid
{
return Ok(nm_ac_obj_path);
}
}
Ok("".into())
}