From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [45.144.208.40]) by lore.proxmox.com (Postfix) with ESMTPS id 7D7271FF0B2 for ; Fri, 25 Sep 2026 14:35:29 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 4F85A216CA; Fri, 25 Sep 2026 14:35:29 +0200 (CEST) From: Christoph Heiss To: pbs-devel@lists.proxmox.com Subject: [PATCH proxmox-backup 3/3] api: network: reuse interface create/update methods from network-api Date: Fri, 25 Sep 2026 14:34:32 +0200 Message-ID: <20260925123459.424903-4-c.heiss@proxmox.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260925123459.424903-1-c.heiss@proxmox.com> References: <20260925123459.424903-1-c.heiss@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1790339721461 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.309 Adjusted score from AWL reputation of From: address DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment (newer systems) RCVD_IN_DNSWL_MED -2.3 Sender listed at https://www.dnswl.org/, medium trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: OPI35KNLFX2ZTRVYM7MPXZWBIKVUYNY2 X-Message-ID-Hash: OPI35KNLFX2ZTRVYM7MPXZWBIKVUYNY2 X-MailFrom: c.heiss@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox Backup Server development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: They are nearly identical in parameters and implementation, only that the network-api crate uses a struct instead of a long list of single parameters. The only real difference are bit more strict checks, namely: - the 'auto' method is now rejected for IPv4, which is IPv6 only - enforces that the method is 'static' when explicitly setting CIDR/GW Signed-off-by: Christoph Heiss --- In combination w/ the earlier patches in the series; also fixes #6121 [0]. [0] https://bugzilla.proxmox.com/show_bug.cgi?id=6121 src/api2/node/network.rs | 674 ++------------------------------------- 1 file changed, 18 insertions(+), 656 deletions(-) diff --git a/src/api2/node/network.rs b/src/api2/node/network.rs index 07ef4bb64..591b4b6e8 100644 --- a/src/api2/node/network.rs +++ b/src/api2/node/network.rs @@ -1,5 +1,4 @@ -use anyhow::{Error, bail}; -use serde::{Deserialize, Serialize}; +use anyhow::Error; use serde_json::{Value, to_value}; use proxmox_router::{ApiMethod, Permission, Router, RpcEnvironment}; @@ -9,86 +8,14 @@ use pbs_api_types::{ Authid, NODE_SCHEMA, PRIV_SYS_AUDIT, PRIV_SYS_MODIFY, PROXMOX_CONFIG_DIGEST_SCHEMA, }; +use proxmox_config_digest::ConfigDigest; use proxmox_network_api::{ - self as network, BondXmitHashPolicy, CIDR_V4_SCHEMA, CIDR_V6_SCHEMA, IP_V4_SCHEMA, - IP_V6_SCHEMA, Interface, LinuxBondMode, NETWORK_INTERFACE_ARRAY_SCHEMA, - NETWORK_INTERFACE_LIST_SCHEMA, NETWORK_INTERFACE_NAME_SCHEMA, NetworkConfig, - NetworkConfigMethod, NetworkInterfaceType, parse_vlan_id_from_name, - parse_vlan_raw_device_from_name, + self as network, DeletableInterfaceProperty, Interface, InterfaceUpdater, + NETWORK_INTERFACE_NAME_SCHEMA, }; use proxmox_rest_server::WorkerTask; -fn split_interface_list(list: &str) -> Result, Error> { - let value = NETWORK_INTERFACE_ARRAY_SCHEMA.parse_property_string(list)?; - Ok(value - .as_array() - .unwrap() - .iter() - .map(|v| v.as_str().unwrap().to_string()) - .collect()) -} - -fn check_duplicate_gateway_v4(config: &NetworkConfig, iface: &str) -> Result<(), Error> { - let current_gateway_v4 = config - .interfaces - .iter() - .find(|(_, interface)| interface.gateway.is_some()) - .map(|(name, _)| name.to_string()); - - if let Some(current_gateway_v4) = current_gateway_v4 { - if current_gateway_v4 != iface { - bail!( - "Default IPv4 gateway already exists on interface '{}'", - current_gateway_v4 - ); - } - } - Ok(()) -} - -fn check_duplicate_gateway_v6(config: &NetworkConfig, iface: &str) -> Result<(), Error> { - let current_gateway_v6 = config - .interfaces - .iter() - .find(|(_, interface)| interface.gateway6.is_some()) - .map(|(name, _)| name.to_string()); - - if let Some(current_gateway_v6) = current_gateway_v6 { - if current_gateway_v6 != iface { - bail!( - "Default IPv6 gateway already exists on interface '{}'", - current_gateway_v6 - ); - } - } - Ok(()) -} - -fn set_bridge_ports(iface: &mut Interface, ports: Vec) -> Result<(), Error> { - if iface.interface_type != NetworkInterfaceType::Bridge { - bail!( - "interface '{}' is no bridge (type is {:?})", - iface.name, - iface.interface_type - ); - } - iface.bridge_ports = Some(ports); - Ok(()) -} - -fn set_bond_slaves(iface: &mut Interface, slaves: Vec) -> Result<(), Error> { - if iface.interface_type != NetworkInterfaceType::Bond { - bail!( - "interface '{}' is no bond (type is {:?})", - iface.name, - iface.interface_type - ); - } - iface.slaves = Some(slaves); - Ok(()) -} - #[api( input: { properties: { @@ -175,89 +102,9 @@ pub fn read_interface(iface: String) -> Result { iface: { schema: NETWORK_INTERFACE_NAME_SCHEMA, }, - "type": { - type: NetworkInterfaceType, - optional: true, - }, - autostart: { - description: "Autostart interface.", - type: bool, - optional: true, - }, - method: { - type: NetworkConfigMethod, - optional: true, - }, - method6: { - type: NetworkConfigMethod, - optional: true, - }, - comments: { - description: "Comments (inet, may span multiple lines)", - type: String, - optional: true, - }, - comments6: { - description: "Comments (inet5, may span multiple lines)", - type: String, - optional: true, - }, - cidr: { - schema: CIDR_V4_SCHEMA, - optional: true, - }, - cidr6: { - schema: CIDR_V6_SCHEMA, - optional: true, - }, - gateway: { - schema: IP_V4_SCHEMA, - optional: true, - }, - gateway6: { - schema: IP_V6_SCHEMA, - optional: true, - }, - mtu: { - description: "Maximum Transmission Unit.", - optional: true, - minimum: 46, - maximum: 65535, - default: 1500, - }, - bridge_ports: { - schema: NETWORK_INTERFACE_LIST_SCHEMA, - optional: true, - }, - bridge_vlan_aware: { - description: "Enable bridge vlan support.", - type: bool, - optional: true, - }, - "vlan-id": { - description: "VLAN ID.", - type: u16, - optional: true, - }, - "vlan-raw-device": { - schema: NETWORK_INTERFACE_NAME_SCHEMA, - optional: true, - }, - bond_mode: { - type: LinuxBondMode, - optional: true, - }, - "bond-primary": { - schema: NETWORK_INTERFACE_NAME_SCHEMA, - optional: true, - }, - bond_xmit_hash_policy: { - type: BondXmitHashPolicy, - optional: true, - }, - slaves: { - schema: NETWORK_INTERFACE_LIST_SCHEMA, - optional: true, + config: { + type: InterfaceUpdater, + flatten: true, }, }, }, @@ -266,210 +113,8 @@ pub fn read_interface(iface: String) -> Result { }, )] /// Create network interface configuration. -#[allow(clippy::too_many_arguments)] -pub fn create_interface( - iface: String, - autostart: Option, - method: Option, - method6: Option, - comments: Option, - comments6: Option, - cidr: Option, - gateway: Option, - cidr6: Option, - gateway6: Option, - mtu: Option, - bridge_ports: Option, - bridge_vlan_aware: Option, - vlan_id: Option, - vlan_raw_device: Option, - bond_mode: Option, - bond_primary: Option, - bond_xmit_hash_policy: Option, - slaves: Option, - param: Value, -) -> Result<(), Error> { - let interface_type = pbs_tools::json::required_string_param(¶m, "type")?; - let interface_type: NetworkInterfaceType = serde_json::from_value(interface_type.into())?; - - let _lock = network::lock_config()?; - - let (mut config, _digest) = network::config()?; - - if 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) = autostart { - interface.autostart = autostart; - } - if method.is_some() { - interface.method = method; - } - if method6.is_some() { - interface.method6 = method6; - } - if mtu.is_some() { - interface.mtu = mtu; - } - if comments.is_some() { - interface.comments = comments; - } - if comments6.is_some() { - interface.comments6 = comments6; - } - - if let Some(cidr) = 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) = 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) = gateway { - let is_v6 = gateway.contains(':'); - if is_v6 { - bail!("invalid address type (expected IPv4, got IPv6)"); - } - check_duplicate_gateway_v4(&config, &iface)?; - interface.gateway = Some(gateway); - } - - if let Some(gateway6) = gateway6 { - let is_v6 = gateway6.contains(':'); - if !is_v6 { - bail!("invalid address type (expected IPv6, got IPv4)"); - } - check_duplicate_gateway_v6(&config, &iface)?; - interface.gateway6 = Some(gateway6); - } - - match interface_type { - NetworkInterfaceType::Bridge => { - if let Some(ports) = bridge_ports { - let ports = split_interface_list(&ports)?; - set_bridge_ports(&mut interface, ports)?; - } - if bridge_vlan_aware.is_some() { - interface.bridge_vlan_aware = bridge_vlan_aware; - } - } - NetworkInterfaceType::Bond => { - if let Some(mode) = bond_mode { - interface.bond_mode = bond_mode; - if bond_primary.is_some() { - if mode != LinuxBondMode::ActiveBackup { - bail!("bond-primary is only valid with Active/Backup mode"); - } - interface.bond_primary = bond_primary; - } - if 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 = bond_xmit_hash_policy; - } - } - if let Some(slaves) = slaves { - let slaves = split_interface_list(&slaves)?; - set_bond_slaves(&mut interface, slaves)?; - } - } - NetworkInterfaceType::Vlan => { - if vlan_id.is_none() && parse_vlan_id_from_name(&iface).is_none() { - bail!("vlan-id must be set"); - } - interface.vlan_id = vlan_id; - - if let Some(dev) = vlan_raw_device - .as_deref() - .or_else(|| parse_vlan_raw_device_from_name(&iface)) - { - if !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 = 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); - } - - config.interfaces.insert(iface, interface); - - network::save_config(&config)?; - - Ok(()) -} - -#[api()] -#[derive(Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] -/// Deletable property name -pub enum DeletableProperty { - /// Delete the IPv4 address property. - Cidr, - /// Delete the IPv6 address property. - Cidr6, - /// Delete the IPv4 gateway property. - Gateway, - /// Delete the IPv6 gateway property. - Gateway6, - /// Delete the whole IPv4 configuration entry. - Method, - /// Delete the whole IPv6 configuration entry. - Method6, - /// Delete IPv4 comments - Comments, - /// Delete IPv6 comments - Comments6, - /// Delete mtu. - Mtu, - /// Delete autostart flag - Autostart, - /// Delete bridge ports (set to 'none') - #[serde(rename = "bridge_ports")] - BridgePorts, - /// Delete bridge-vlan-aware flag - #[serde(rename = "bridge_vlan_aware")] - BridgeVlanAware, - /// Delete bond-slaves (set to 'none') - Slaves, - /// Delete bond-primary - BondPrimary, - /// Delete bond transmit hash policy - #[serde(rename = "bond_xmit_hash_policy")] - BondXmitHashPolicy, +pub fn create_interface(iface: String, config: InterfaceUpdater) -> Result<(), Error> { + proxmox_network_api::create_interface(iface, config) } #[api( @@ -482,101 +127,21 @@ pub enum DeletableProperty { iface: { schema: NETWORK_INTERFACE_NAME_SCHEMA, }, - "type": { - type: NetworkInterfaceType, - optional: true, - }, - autostart: { - description: "Autostart interface.", - type: bool, - optional: true, - }, - method: { - type: NetworkConfigMethod, - optional: true, - }, - method6: { - type: NetworkConfigMethod, - optional: true, - }, - comments: { - description: "Comments (inet, may span multiple lines)", - type: String, - optional: true, - }, - comments6: { - description: "Comments (inet5, may span multiple lines)", - type: String, - optional: true, - }, - cidr: { - schema: CIDR_V4_SCHEMA, - optional: true, - }, - cidr6: { - schema: CIDR_V6_SCHEMA, - optional: true, - }, - gateway: { - schema: IP_V4_SCHEMA, - optional: true, - }, - gateway6: { - schema: IP_V6_SCHEMA, - optional: true, - }, - mtu: { - description: "Maximum Transmission Unit.", - optional: true, - minimum: 46, - maximum: 65535, - default: 1500, - }, - bridge_ports: { - schema: NETWORK_INTERFACE_LIST_SCHEMA, - optional: true, - }, - bridge_vlan_aware: { - description: "Enable bridge vlan support.", - type: bool, - optional: true, - }, - "vlan-id": { - description: "VLAN ID.", - type: u16, - optional: true, - }, - "vlan-raw-device": { - schema: NETWORK_INTERFACE_NAME_SCHEMA, - optional: true, - }, - bond_mode: { - type: LinuxBondMode, - optional: true, - }, - "bond-primary": { - schema: NETWORK_INTERFACE_NAME_SCHEMA, - optional: true, - }, - bond_xmit_hash_policy: { - type: BondXmitHashPolicy, - optional: true, - }, - slaves: { - schema: NETWORK_INTERFACE_LIST_SCHEMA, - optional: true, + update: { + type: InterfaceUpdater, + flatten: true, }, delete: { description: "List of properties to delete.", type: Array, optional: true, items: { - type: DeletableProperty, + type: DeletableInterfaceProperty, } }, digest: { + type: ConfigDigest, optional: true, - schema: PROXMOX_CONFIG_DIGEST_SCHEMA, }, }, }, @@ -585,216 +150,13 @@ pub enum DeletableProperty { }, )] /// Update network interface config. -#[allow(clippy::too_many_arguments)] pub fn update_interface( iface: String, - autostart: Option, - method: Option, - method6: Option, - comments: Option, - comments6: Option, - cidr: Option, - gateway: Option, - cidr6: Option, - gateway6: Option, - mtu: Option, - bridge_ports: Option, - bridge_vlan_aware: Option, - vlan_id: Option, - vlan_raw_device: Option, - bond_mode: Option, - bond_primary: Option, - bond_xmit_hash_policy: Option, - slaves: Option, - delete: Option>, - digest: Option, - param: Value, + update: InterfaceUpdater, + delete: Option>, + digest: Option, ) -> Result<(), Error> { - let _lock = network::lock_config()?; - - let (mut config, expected_digest) = network::config()?; - - pbs_config::detect_modified_configuration_file(digest, &expected_digest)?; - - if gateway.is_some() { - check_duplicate_gateway_v4(&config, &iface)?; - } - if gateway6.is_some() { - check_duplicate_gateway_v6(&config, &iface)?; - } - - if let Some(dev) = vlan_raw_device - .as_deref() - .or_else(|| parse_vlan_raw_device_from_name(&iface)) - { - if !config.interfaces.contains_key(dev) { - bail!("vlan-raw-device {dev} does not exist"); - } - } - - let interface = config.lookup_mut(&iface)?; - - if let Some(interface_type) = param.get("type") { - let interface_type = NetworkInterfaceType::deserialize(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 { - DeletableProperty::Cidr => { - interface.cidr = None; - } - DeletableProperty::Cidr6 => { - interface.cidr6 = None; - } - DeletableProperty::Gateway => { - interface.gateway = None; - } - DeletableProperty::Gateway6 => { - interface.gateway6 = None; - } - DeletableProperty::Method => { - interface.method = None; - } - DeletableProperty::Method6 => { - interface.method6 = None; - } - DeletableProperty::Comments => { - interface.comments = None; - } - DeletableProperty::Comments6 => { - interface.comments6 = None; - } - DeletableProperty::Mtu => { - interface.mtu = None; - } - DeletableProperty::Autostart => { - interface.autostart = false; - } - DeletableProperty::BridgePorts => { - set_bridge_ports(interface, Vec::new())?; - } - DeletableProperty::BridgeVlanAware => { - interface.bridge_vlan_aware = None; - } - DeletableProperty::Slaves => { - set_bond_slaves(interface, Vec::new())?; - } - DeletableProperty::BondPrimary => { - interface.bond_primary = None; - } - DeletableProperty::BondXmitHashPolicy => interface.bond_xmit_hash_policy = None, - } - } - } - - if let Some(autostart) = autostart { - interface.autostart = autostart; - } - if method.is_some() { - interface.method = method; - } - if method6.is_some() { - interface.method6 = method6; - } - if mtu.is_some() { - interface.mtu = mtu; - } - if let Some(ports) = bridge_ports { - let ports = split_interface_list(&ports)?; - set_bridge_ports(interface, ports)?; - } - if bridge_vlan_aware.is_some() { - interface.bridge_vlan_aware = bridge_vlan_aware; - } - if let Some(slaves) = slaves { - let slaves = split_interface_list(&slaves)?; - set_bond_slaves(interface, slaves)?; - } - if let Some(mode) = bond_mode { - interface.bond_mode = bond_mode; - if bond_primary.is_some() { - if mode != LinuxBondMode::ActiveBackup { - bail!("bond-primary is only valid with Active/Backup mode"); - } - interface.bond_primary = bond_primary; - } - if 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 = bond_xmit_hash_policy; - } - } - - if let Some(cidr) = 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) = 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) = 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) = gateway6 { - let is_v6 = gateway6.contains(':'); - if !is_v6 { - bail!("invalid address type (expected IPv6, got IPv4)"); - } - interface.gateway6 = Some(gateway6); - } - - if comments.is_some() { - interface.comments = comments; - } - if comments6.is_some() { - interface.comments6 = 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 if interface.method6.is_none() { - interface.method6 = Some(NetworkConfigMethod::Manual); - } - - if vlan_id.is_some() { - interface.vlan_id = vlan_id; - } - if vlan_raw_device.is_some() { - interface.vlan_raw_device = vlan_raw_device; - } - - network::save_config(&config)?; - - Ok(()) + proxmox_network_api::update_interface(iface, update, delete, digest) } #[api( -- 2.55.0