* [PATCH proxmox-backup 3/3] api: network: reuse interface create/update methods from network-api
2026-09-25 12:34 [PATCH proxmox{,-backup} 0/3] fix #6121: add 'bond-miimon' option for network interfaces Christoph Heiss
2026-09-25 12:34 ` [PATCH proxmox 1/3] fix #6121: network-api: api: add `bond-miimon` option for interfaces Christoph Heiss
2026-09-25 12:34 ` [PATCH proxmox 2/3] fix #6121: network-api: config: add support for `bond-miimon` option Christoph Heiss
@ 2026-09-25 12:34 ` Christoph Heiss
2 siblings, 0 replies; 4+ messages in thread
From: Christoph Heiss @ 2026-09-25 12:34 UTC (permalink / raw)
To: pbs-devel
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 <c.heiss@proxmox.com>
---
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<Vec<String>, 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<String>) -> 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<String>) -> 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<Value, Error> {
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<Value, Error> {
},
)]
/// Create network interface configuration.
-#[allow(clippy::too_many_arguments)]
-pub fn create_interface(
- iface: String,
- autostart: Option<bool>,
- method: Option<NetworkConfigMethod>,
- method6: Option<NetworkConfigMethod>,
- comments: Option<String>,
- comments6: Option<String>,
- cidr: Option<String>,
- gateway: Option<String>,
- cidr6: Option<String>,
- gateway6: Option<String>,
- mtu: Option<u64>,
- bridge_ports: Option<String>,
- bridge_vlan_aware: Option<bool>,
- vlan_id: Option<u16>,
- vlan_raw_device: Option<String>,
- bond_mode: Option<LinuxBondMode>,
- bond_primary: Option<String>,
- bond_xmit_hash_policy: Option<BondXmitHashPolicy>,
- slaves: Option<String>,
- 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<bool>,
- method: Option<NetworkConfigMethod>,
- method6: Option<NetworkConfigMethod>,
- comments: Option<String>,
- comments6: Option<String>,
- cidr: Option<String>,
- gateway: Option<String>,
- cidr6: Option<String>,
- gateway6: Option<String>,
- mtu: Option<u64>,
- bridge_ports: Option<String>,
- bridge_vlan_aware: Option<bool>,
- vlan_id: Option<u16>,
- vlan_raw_device: Option<String>,
- bond_mode: Option<LinuxBondMode>,
- bond_primary: Option<String>,
- bond_xmit_hash_policy: Option<BondXmitHashPolicy>,
- slaves: Option<String>,
- delete: Option<Vec<DeletableProperty>>,
- digest: Option<String>,
- param: Value,
+ update: InterfaceUpdater,
+ delete: Option<Vec<DeletableInterfaceProperty>>,
+ digest: Option<ConfigDigest>,
) -> 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
^ permalink raw reply related [flat|nested] 4+ messages in thread