use std::collections::HashSet;
use super::{
NmConnectionMatcher,
device::create_index_for_nm_devs,
nm_dbus::{NmConnection, NmDevice, NmIfaceType},
settings::{fix_ip_dhcp_timeout, iface_to_nm_connections},
};
use crate::{
InterfaceType, MergedInterface, MergedInterfaces, MergedNetworkState,
NmstateError,
};
#[cfg_attr(not(feature = "query_apply"), allow(dead_code))]
pub(crate) struct PreparedNmConnections {
pub(crate) to_store: Vec<NmConnection>,
pub(crate) to_activate: Vec<NmConnection>,
pub(crate) to_deactivate: Vec<NmDevice>,
pub(crate) to_delete: Vec<String>,
}
pub(crate) fn prepare_nm_conns(
merged_state: &MergedNetworkState,
conn_matcher: &NmConnectionMatcher,
nm_devs: &[NmDevice],
gen_conf_mode: bool,
is_retry: bool,
forwarding_supported: bool,
) -> Result<PreparedNmConnections, NmstateError> {
let mut nm_conns_to_update: Vec<NmConnection> = Vec::new();
let mut nm_conns_to_activate: Vec<NmConnection> = Vec::new();
let mut ifaces: Vec<&MergedInterface> = merged_state
.interfaces
.iter()
.filter(|i| i.is_changed())
.collect();
ifaces.sort_unstable_by_key(|iface| iface.merged.name());
ifaces.sort_by_key(|iface| {
if let Some(i) = iface.for_apply.as_ref() {
i.base_iface().up_priority
} else {
u32::MAX
}
});
let nm_devs_indexed = create_index_for_nm_devs(nm_devs);
let mut nm_devs_to_deactivate: Vec<NmDevice> = ifaces
.iter()
.filter(|iface| {
iface.for_apply.as_ref().map(|i| i.is_down()) == Some(true)
})
.filter_map(|iface| {
nm_devs_indexed
.get(&(iface.merged.name(), iface.merged.iface_type()))
.copied()
})
.cloned()
.collect();
for merged_iface in ifaces.iter().filter(|i| {
i.merged.iface_type() != InterfaceType::Unknown && !i.merged.is_absent()
}) {
let iface = if let Some(i) = merged_iface.for_apply.as_ref() {
i
} else {
continue;
};
for mut nm_conn in iface_to_nm_connections(
merged_iface,
merged_state,
conn_matcher,
gen_conf_mode,
)? {
if !forwarding_supported {
if let Some(ipv4) = nm_conn.ipv4.as_mut() {
ipv4.forwarding = None;
}
if let Some(desired_iface) = merged_iface.desired.as_ref() {
let forwarding_desired = desired_iface
.base_iface()
.ipv4
.as_ref()
.and_then(|ip4| ip4.forwarding)
.is_some();
if forwarding_desired {
log::warn!(
"Clearing unsupported ipv4.forwarding for \
interface '{}'",
desired_iface.name()
);
}
}
}
if iface.is_up()
&& (is_retry
|| !can_skip_activation(
merged_iface,
&merged_state.interfaces,
&nm_conn,
conn_matcher,
))
{
nm_conns_to_activate.push(nm_conn.clone());
}
if iface.is_down()
&& merged_iface.current.as_ref().map(|i| i.is_ignore())
== Some(true)
{
nm_conns_to_activate.push(nm_conn.clone());
if let Some(&nm_dev) =
nm_devs_indexed.get(&(iface.name(), iface.iface_type()))
{
nm_devs_to_deactivate.push(nm_dev.clone());
}
}
if iface.is_down()
&& gen_conf_mode
&& let Some(nm_conn_set) = nm_conn.connection.as_mut()
{
nm_conn_set.autoconnect = Some(false);
}
nm_conns_to_update.push(nm_conn);
}
}
fix_ip_dhcp_timeout(&mut nm_conns_to_update);
let mut ret = PreparedNmConnections {
to_store: nm_conns_to_update,
to_activate: nm_conns_to_activate,
to_deactivate: nm_devs_to_deactivate,
to_delete: Vec::new(),
};
detach_saved_nm_conn_from_controller(
&mut ret,
&merged_state.interfaces,
conn_matcher,
);
Ok(ret)
}
fn can_skip_activation(
merged_iface: &MergedInterface,
merged_ifaces: &MergedInterfaces,
nm_conn: &NmConnection,
conn_matcher: &NmConnectionMatcher,
) -> bool {
if let Some(desired_iface) = merged_iface.for_apply.as_ref()
&& let (Some(ctrl_iface), Some(ctrl_type)) = (
desired_iface.base_iface().controller.as_deref(),
desired_iface.base_iface().controller_type.as_ref(),
)
&& let Some(merged_ctrl_iface) =
merged_ifaces.get_iface(ctrl_iface, ctrl_type.clone())
&& merged_ctrl_iface.for_apply.is_some()
&& (merged_ctrl_iface.merged.is_absent()
|| merged_ctrl_iface.merged.is_down())
{
log::info!(
"Skipping activation of {} as its controller {} desire to be down \
or absent",
merged_iface.merged.name(),
ctrl_iface
);
return true;
}
if let Some(uuid) = nm_conn.uuid()
&& conn_matcher.is_activated(uuid)
{
return false;
}
if merged_iface.current.is_none()
&& merged_iface.for_apply.is_some()
&& merged_iface.merged.is_up()
&& let Some(desired_iface) = merged_iface.for_apply.as_ref()
{
if let (Some(ctrl_iface), Some(ctrl_type)) = (
desired_iface.base_iface().controller.as_deref(),
desired_iface.base_iface().controller_type.as_ref(),
) && let Some(merged_ctrl_iface) =
merged_ifaces.get_iface(ctrl_iface, ctrl_type.clone())
&& merged_ctrl_iface.current.is_none()
&& merged_ctrl_iface.for_apply.is_some()
&& merged_ctrl_iface.merged.is_up()
{
log::info!(
"Skipping activation of {} as its controller {} will \
automatically activate it",
merged_iface.merged.name(),
ctrl_iface
);
return true;
}
if nm_conn.iface_type() == Some(&NmIfaceType::OvsPort) {
return true;
}
}
false
}
fn detach_saved_nm_conn_from_controller(
prepared_conns: &mut PreparedNmConnections,
merged_ifaces: &MergedInterfaces,
conn_matcher: &NmConnectionMatcher,
) {
let saved_inactive_port_nm_conn: Vec<&NmConnection> = conn_matcher
.saved_iter()
.filter(|nm_conn| {
if let Some(uuid) = nm_conn.uuid() {
!conn_matcher.is_uuid_activated(uuid)
&& !is_prepared_to_store(prepared_conns, uuid)
&& nm_conn.controller().is_some()
} else {
false
}
})
.collect();
if saved_inactive_port_nm_conn.is_empty() {
return;
}
let mut changed_ctrl_names: HashSet<(&str, NmIfaceType)> = HashSet::new();
let mut changed_ctrl_uuids: HashSet<(&str, NmIfaceType)> = HashSet::new();
for merged_iface in merged_ifaces.iter().filter(|merged_iface| {
is_ctrl_iface_desired_port_changes(merged_iface)
&& merged_iface.for_apply.as_ref().map(|i| i.is_up()) == Some(true)
}) {
let ctrl_iface_name = merged_iface.merged.name();
let ctrl_iface_type = &merged_iface.merged.base_iface().iface_type;
let ctrl_nm_iface_type = NmIfaceType::from(ctrl_iface_type);
if let Some(ctrl_nm_conn) = prepared_conns
.to_activate
.as_slice()
.iter()
.find(|nm_conn| {
nm_conn.iface_name() == Some(ctrl_iface_name)
&& nm_conn.iface_type() == Some(&ctrl_nm_iface_type)
})
&& let Some(uuid) = ctrl_nm_conn.uuid()
{
changed_ctrl_names.insert((ctrl_iface_name, ctrl_nm_iface_type));
changed_ctrl_uuids.insert((uuid, ctrl_nm_iface_type));
}
}
let mut orphan_ovs_port_uuids: HashSet<&str> = HashSet::new();
let mut orphan_ovs_port_names: HashSet<&str> = HashSet::new();
for nm_conn in saved_inactive_port_nm_conn.as_slice() {
if let Some(ctrl_name) = nm_conn.controller()
&& let Some(nm_ctrl_type) = nm_conn.controller_type()
&& ((is_uuid(ctrl_name)
&& changed_ctrl_uuids.contains(&(ctrl_name, *nm_ctrl_type)))
|| changed_ctrl_names.contains(&(ctrl_name, *nm_ctrl_type)))
{
if nm_conn.iface_type() == Some(&NmIfaceType::OvsPort) {
if let Some(uuid) = nm_conn.uuid() {
log::debug!("Deleting orphan OVS port {uuid}");
log::info!(
"Deleting connection {uuid}: {}/ovs-port",
nm_conn.iface_name().unwrap_or_default(),
);
prepared_conns.to_delete.push(uuid.to_string());
orphan_ovs_port_uuids.insert(uuid);
if let Some(ovs_port_iface_name) = nm_conn.iface_name() {
orphan_ovs_port_names.insert(ovs_port_iface_name);
}
}
} else {
let mut changed_nm_conn = (*nm_conn).clone();
if let Some(nm_conn_set) = changed_nm_conn.connection.as_mut() {
log::debug!(
"Detaching inactive controller port {}: {}/{}",
nm_conn.uuid().unwrap_or_default(),
nm_conn.iface_name().unwrap_or_default(),
nm_conn.iface_type().unwrap_or(&NmIfaceType::Unknown)
);
nm_conn_set.controller = None;
nm_conn_set.controller_type = None;
changed_nm_conn.bond_port = None;
changed_nm_conn.bridge_port = None;
prepared_conns.to_store.push(changed_nm_conn);
}
}
}
}
for nm_conn in saved_inactive_port_nm_conn {
if nm_conn.controller_type() == Some(&NmIfaceType::OvsPort)
&& let Some(ctrl_name) = nm_conn.controller()
&& (is_uuid(ctrl_name)
&& orphan_ovs_port_uuids.contains(&ctrl_name)
|| orphan_ovs_port_names.contains(&ctrl_name))
{
if nm_conn.iface_type() == Some(&NmIfaceType::OvsIface) {
if let Some(uuid) = nm_conn.uuid() {
prepared_conns.to_delete.push(uuid.to_string());
}
} else {
let mut changed_nm_conn = nm_conn.clone();
if let Some(nm_conn_set) = changed_nm_conn.connection.as_mut() {
log::debug!(
"Detaching inactive OVS interface {}: {}/{}",
nm_conn.uuid().unwrap_or_default(),
nm_conn.iface_name().unwrap_or_default(),
nm_conn.iface_type().unwrap_or(&NmIfaceType::Unknown)
);
nm_conn_set.controller = None;
nm_conn_set.controller_type = None;
changed_nm_conn.ovs_iface = None;
prepared_conns.to_store.push(changed_nm_conn);
}
}
}
}
}
fn is_prepared_to_store(
prepared_conns: &PreparedNmConnections,
uuid: &str,
) -> bool {
prepared_conns
.to_store
.as_slice()
.iter()
.any(|nm_conn| nm_conn.uuid() == Some(uuid))
}
fn is_ctrl_iface_desired_port_changes(merged_iface: &MergedInterface) -> bool {
merged_iface.for_apply.as_ref().map(|i| i.ports().is_some()) == Some(true)
}
pub(crate) fn is_uuid(value: &str) -> bool {
uuid::Uuid::parse_str(value).is_ok()
}