From b082d7dafaf22aedb033a32a933a4db603170872 Mon Sep 17 00:00:00 2001 From: Dietmar Maurer Date: Thu, 16 May 2024 10:24:41 +0200 Subject: [PATCH] system-management-api: network: add create_interface and update_interface Signed-off-by: Dietmar Maurer --- .../src/network/api_impl.rs | 347 ++++++++++++++++++ .../src/network/mod.rs | 5 + 2 files changed, 352 insertions(+) create mode 100644 proxmox-system-management-api/src/network/api_impl.rs diff --git a/proxmox-system-management-api/src/network/api_impl.rs b/proxmox-system-management-api/src/network/api_impl.rs new file mode 100644 index 00000000..59e55f8d --- /dev/null +++ b/proxmox-system-management-api/src/network/api_impl.rs @@ -0,0 +1,347 @@ +use anyhow::{bail, Error}; + +use proxmox_product_config::ConfigDigest; + +use crate::network::{self, parse_vlan_id_from_name, parse_vlan_raw_device_from_name}; +use crate::network::{ + DeletableInterfaceProperty, Interface, InterfaceUpdater, LinuxBondMode, NetworkConfigMethod, + NetworkInterfaceType, +}; + +/// Create network interface configuration. +pub fn create_interface(iface: String, config: InterfaceUpdater) -> Result<(), Error> { + let interface_type = match config.interface_type { + Some(interface_type) => interface_type, + None => bail!("missing parameter 'type'"), + }; + + let _lock = network::lock_config()?; + + let (mut network_config, _digest) = network::config()?; + + if network_config.interfaces.contains_key(&iface) { + bail!("interface '{}' already exists", iface); + } + + let mut interface = Interface::new(iface.clone()); + interface.interface_type = interface_type; + + if let Some(autostart) = config.autostart { + interface.autostart = autostart; + } + if config.method.is_some() { + interface.method = config.method; + } + if config.method6.is_some() { + interface.method6 = config.method6; + } + if config.mtu.is_some() { + interface.mtu = config.mtu; + } + if config.comments.is_some() { + interface.comments = config.comments; + } + if config.comments6.is_some() { + interface.comments6 = config.comments6; + } + + if let Some(cidr) = config.cidr { + let (_, _, is_v6) = network::parse_cidr(&cidr)?; + if is_v6 { + bail!("invalid address type (expected IPv4, got IPv6)"); + } + interface.cidr = Some(cidr); + } + + if let Some(cidr6) = config.cidr6 { + let (_, _, is_v6) = network::parse_cidr(&cidr6)?; + if !is_v6 { + bail!("invalid address type (expected IPv6, got IPv4)"); + } + interface.cidr6 = Some(cidr6); + } + + if let Some(gateway) = config.gateway { + let is_v6 = gateway.contains(':'); + if is_v6 { + bail!("invalid address type (expected IPv4, got IPv6)"); + } + network_config.check_duplicate_gateway_v4(&iface)?; + interface.gateway = Some(gateway); + } + + if let Some(gateway6) = config.gateway6 { + let is_v6 = gateway6.contains(':'); + if !is_v6 { + bail!("invalid address type (expected IPv6, got IPv4)"); + } + network_config.check_duplicate_gateway_v6(&iface)?; + interface.gateway6 = Some(gateway6); + } + + match interface_type { + NetworkInterfaceType::Bridge => { + if let Some(ports) = &config.bridge_ports { + interface.set_bridge_port_list(ports)?; + } + if config.bridge_vlan_aware.is_some() { + interface.bridge_vlan_aware = config.bridge_vlan_aware; + } + } + NetworkInterfaceType::Bond => { + if let Some(mode) = config.bond_mode { + interface.bond_mode = config.bond_mode; + if config.bond_primary.is_some() { + if mode != LinuxBondMode::ActiveBackup { + bail!("bond-primary is only valid with Active/Backup mode"); + } + interface.bond_primary = config.bond_primary; + } + if config.bond_xmit_hash_policy.is_some() { + if mode != LinuxBondMode::Ieee802_3ad && mode != LinuxBondMode::BalanceXor { + bail!("bond_xmit_hash_policy is only valid with LACP(802.3ad) or balance-xor mode"); + } + interface.bond_xmit_hash_policy = config.bond_xmit_hash_policy; + } + } + if let Some(slaves) = &config.slaves { + interface.set_bond_slave_list(&slaves)?; + } + } + NetworkInterfaceType::Vlan => { + if config.vlan_id.is_none() && parse_vlan_id_from_name(&iface).is_none() { + bail!("vlan-id must be set"); + } + interface.vlan_id = config.vlan_id; + + if let Some(dev) = config + .vlan_raw_device + .as_deref() + .or_else(|| parse_vlan_raw_device_from_name(&iface)) + { + if !network_config.interfaces.contains_key(dev) { + bail!("vlan-raw-device {dev} does not exist"); + } + } else { + bail!("vlan-raw-device must be set"); + } + interface.vlan_raw_device = config.vlan_raw_device; + } + _ => bail!( + "creating network interface type '{:?}' is not supported", + interface_type + ), + } + + if interface.cidr.is_some() || interface.gateway.is_some() { + interface.method = Some(NetworkConfigMethod::Static); + } else if interface.method.is_none() { + interface.method = Some(NetworkConfigMethod::Manual); + } + + if interface.cidr6.is_some() || interface.gateway6.is_some() { + interface.method6 = Some(NetworkConfigMethod::Static); + } else if interface.method6.is_none() { + interface.method6 = Some(NetworkConfigMethod::Manual); + } + + network_config.interfaces.insert(iface, interface); + + network::save_config(&network_config)?; + + Ok(()) +} + +/// Update network interface config. +pub fn update_interface( + iface: String, + update: InterfaceUpdater, + delete: Option>, + digest: Option, +) -> Result<(), Error> { + let _lock = network::lock_config()?; + + let (mut network_config, expected_digest) = network::config()?; + + expected_digest.detect_modification(digest.as_ref())?; + + if update.gateway.is_some() { + network_config.check_duplicate_gateway_v4(&iface)?; + } + if update.gateway6.is_some() { + network_config.check_duplicate_gateway_v6(&iface)?; + } + + if let Some(dev) = update + .vlan_raw_device + .as_deref() + .or_else(|| parse_vlan_raw_device_from_name(&iface)) + { + if !network_config.interfaces.contains_key(dev) { + bail!("vlan-raw-device {dev} does not exist"); + } + } + + let interface = network_config.lookup_mut(&iface)?; + + if let Some(interface_type) = update.interface_type { + if interface_type != interface.interface_type { + bail!( + "got unexpected interface type ({:?} != {:?})", + interface_type, + interface.interface_type + ); + } + } + + if let Some(delete) = delete { + for delete_prop in delete { + match delete_prop { + DeletableInterfaceProperty::Cidr => { + interface.cidr = None; + } + DeletableInterfaceProperty::Cidr6 => { + interface.cidr6 = None; + } + DeletableInterfaceProperty::Gateway => { + interface.gateway = None; + } + DeletableInterfaceProperty::Gateway6 => { + interface.gateway6 = None; + } + DeletableInterfaceProperty::Method => { + interface.method = None; + } + DeletableInterfaceProperty::Method6 => { + interface.method6 = None; + } + DeletableInterfaceProperty::Comments => { + interface.comments = None; + } + DeletableInterfaceProperty::Comments6 => { + interface.comments6 = None; + } + DeletableInterfaceProperty::Mtu => { + interface.mtu = None; + } + DeletableInterfaceProperty::Autostart => { + interface.autostart = false; + } + DeletableInterfaceProperty::BridgePorts => { + interface.set_bridge_ports(Vec::new())?; + } + DeletableInterfaceProperty::BridgeVlanAware => { + interface.bridge_vlan_aware = None; + } + DeletableInterfaceProperty::Slaves => { + interface.set_bond_slaves(Vec::new())?; + } + DeletableInterfaceProperty::BondPrimary => { + interface.bond_primary = None; + } + DeletableInterfaceProperty::BondXmitHashPolicy => { + interface.bond_xmit_hash_policy = None + } + } + } + } + + if let Some(autostart) = update.autostart { + interface.autostart = autostart; + } + if update.method.is_some() { + interface.method = update.method; + } + if update.method6.is_some() { + interface.method6 = update.method6; + } + if update.mtu.is_some() { + interface.mtu = update.mtu; + } + if let Some(ports) = &update.bridge_ports { + interface.set_bridge_port_list(ports)?; + } + if update.bridge_vlan_aware.is_some() { + interface.bridge_vlan_aware = update.bridge_vlan_aware; + } + if let Some(slaves) = &update.slaves { + interface.set_bond_slave_list(slaves)?; + } + if let Some(mode) = update.bond_mode { + interface.bond_mode = update.bond_mode; + if update.bond_primary.is_some() { + if mode != LinuxBondMode::ActiveBackup { + bail!("bond-primary is only valid with Active/Backup mode"); + } + interface.bond_primary = update.bond_primary; + } + if update.bond_xmit_hash_policy.is_some() { + if mode != LinuxBondMode::Ieee802_3ad && mode != LinuxBondMode::BalanceXor { + bail!("bond_xmit_hash_policy is only valid with LACP(802.3ad) or balance-xor mode"); + } + interface.bond_xmit_hash_policy = update.bond_xmit_hash_policy; + } + } + + if let Some(cidr) = update.cidr { + let (_, _, is_v6) = network::parse_cidr(&cidr)?; + if is_v6 { + bail!("invalid address type (expected IPv4, got IPv6)"); + } + interface.cidr = Some(cidr); + } + + if let Some(cidr6) = update.cidr6 { + let (_, _, is_v6) = network::parse_cidr(&cidr6)?; + if !is_v6 { + bail!("invalid address type (expected IPv6, got IPv4)"); + } + interface.cidr6 = Some(cidr6); + } + + if let Some(gateway) = update.gateway { + let is_v6 = gateway.contains(':'); + if is_v6 { + bail!("invalid address type (expected IPv4, got IPv6)"); + } + interface.gateway = Some(gateway); + } + + if let Some(gateway6) = update.gateway6 { + let is_v6 = gateway6.contains(':'); + if !is_v6 { + bail!("invalid address type (expected IPv6, got IPv4)"); + } + interface.gateway6 = Some(gateway6); + } + + if update.comments.is_some() { + interface.comments = update.comments; + } + if update.comments6.is_some() { + interface.comments6 = update.comments6; + } + + if interface.cidr.is_some() || interface.gateway.is_some() { + interface.method = Some(NetworkConfigMethod::Static); + } else { + interface.method = Some(NetworkConfigMethod::Manual); + } + + if interface.cidr6.is_some() || interface.gateway6.is_some() { + interface.method6 = Some(NetworkConfigMethod::Static); + } else { + interface.method6 = Some(NetworkConfigMethod::Manual); + } + + if update.vlan_id.is_some() { + interface.vlan_id = update.vlan_id; + } + if update.vlan_raw_device.is_some() { + interface.vlan_raw_device = update.vlan_raw_device; + } + + network::save_config(&network_config)?; + + Ok(()) +} diff --git a/proxmox-system-management-api/src/network/mod.rs b/proxmox-system-management-api/src/network/mod.rs index 1ac47f2a..6cd38a8e 100644 --- a/proxmox-system-management-api/src/network/mod.rs +++ b/proxmox-system-management-api/src/network/mod.rs @@ -6,3 +6,8 @@ pub use api_types::*; mod config; #[cfg(feature = "network-impl")] pub use config::*; + +#[cfg(feature = "network-impl")] +mod api_impl; +#[cfg(feature = "network-impl")] +pub use api_impl::{create_interface, update_interface};