diff --git a/Cargo.toml b/Cargo.toml index 84c92b86..16730f02 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,7 @@ pam = "0.7" pam-sys = "0.5" percent-encoding = "2.1" pin-utils = "0.1.0-alpha" -proxmox = { version = "0.1.25", features = [ "sortable-macro", "api-macro" ] } +proxmox = { version = "0.1.26", features = [ "sortable-macro", "api-macro" ] } #proxmox = { git = "ssh://gitolite3@proxdev.maurer-it.com/rust/proxmox", version = "0.1.2", features = [ "sortable-macro", "api-macro" ] } #proxmox = { path = "../proxmox/proxmox", features = [ "sortable-macro", "api-macro" ] } regex = "1.2" diff --git a/src/api2/node/network.rs b/src/api2/node/network.rs index 9003808d..0160cef6 100644 --- a/src/api2/node/network.rs +++ b/src/api2/node/network.rs @@ -7,6 +7,7 @@ use proxmox::api::{api, ApiMethod, Router, RpcEnvironment, Permission}; use crate::config::network; use crate::config::acl::{PRIV_SYS_AUDIT, PRIV_SYS_MODIFY}; use crate::api2::types::*; +use crate::server::{WorkerTask}; #[api( input: { @@ -39,9 +40,11 @@ pub fn list_network_devices( let mut list = Vec::new(); - for interface in config.interfaces.values() { + for (iface, interface) in config.interfaces.iter() { + if iface == "lo" { continue; } // do not list lo let mut item: Value = to_value(interface)?; item["digest"] = digest.clone().into(); + item["iface"] = iface.to_string().into(); list.push(item); } @@ -54,12 +57,12 @@ pub fn list_network_devices( } #[api( - input: { + input: { properties: { node: { schema: NODE_SCHEMA, }, - name: { + iface: { schema: NETWORK_INTERFACE_NAME_SCHEMA, }, }, @@ -73,11 +76,11 @@ pub fn list_network_devices( }, )] /// Read a network interface configuration. -pub fn read_interface(name: String) -> Result { +pub fn read_interface(iface: String) -> Result { let (config, digest) = network::config()?; - let interface = config.lookup(&name)?; + let interface = config.lookup(&iface)?; let mut data: Value = to_value(interface)?; data["digest"] = proxmox::tools::digest_to_hex(&digest).into(); @@ -91,27 +94,29 @@ pub fn read_interface(name: String) -> Result { /// Deletable property name pub enum DeletableProperty { /// Delete the IPv4 address property. - address_v4, + cidr, /// Delete the IPv6 address property. - address_v6, + cidr6, /// Delete the IPv4 gateway property. - gateway_v4, + gateway, /// Delete the IPv6 gateway property. - gateway_v6, + gateway6, /// Delete the whole IPv4 configuration entry. - method_v4, + method, /// Delete the whole IPv6 configuration entry. - method_v6, + method6, /// Delete IPv4 comments - comments_v4, + comments, /// Delete IPv6 comments - comments_v6, + comments6, /// Delete mtu. mtu, - /// Delete auto flag - auto, + /// Delete autostart flag + autostart, /// Delete bridge ports (set to 'none') bridge_ports, + /// Delet bridge-vlan-aware flag + bridge_vlan_aware, /// Delete bond-slaves (set to 'none') bond_slaves, } @@ -124,38 +129,51 @@ pub enum DeletableProperty { node: { schema: NODE_SCHEMA, }, - name: { + iface: { schema: NETWORK_INTERFACE_NAME_SCHEMA, }, - auto: { + "type": { + description: "Interface type. If specified, need to match the current type.", + type: NetworkInterfaceType, + optional: true, + }, + autostart: { description: "Autostart interface.", type: bool, optional: true, }, - method_v4: { + method: { type: NetworkConfigMethod, optional: true, }, - method_v6: { + method6: { type: NetworkConfigMethod, optional: true, }, - comments_v4: { + comments: { description: "Comments (inet, may span multiple lines)", type: String, optional: true, }, - comments_v6: { + comments6: { description: "Comments (inet5, may span multiple lines)", type: String, optional: true, }, - address: { - schema: CIDR_SCHEMA, + cidr: { + schema: CIDR_V4_SCHEMA, + optional: true, + }, + cidr6: { + schema: CIDR_V6_SCHEMA, optional: true, }, gateway: { - schema: IP_SCHEMA, + schema: IP_V4_SCHEMA, + optional: true, + }, + gateway6: { + schema: IP_V6_SCHEMA, optional: true, }, mtu: { @@ -169,6 +187,11 @@ pub enum DeletableProperty { schema: NETWORK_INTERFACE_LIST_SCHEMA, optional: true, }, + bridge_vlan_aware: { + description: "Enable bridge vlan support.", + type: bool, + optional: true, + }, bond_slaves: { schema: NETWORK_INTERFACE_LIST_SCHEMA, optional: true, @@ -188,24 +211,28 @@ pub enum DeletableProperty { }, }, access: { - permission: &Permission::Privilege(&["system", "network", "interfaces", "{name}"], PRIV_SYS_MODIFY, false), + permission: &Permission::Privilege(&["system", "network", "interfaces", "{iface}"], PRIV_SYS_MODIFY, false), }, )] /// Update network interface config. pub fn update_interface( - name: String, - auto: Option, - method_v4: Option, - method_v6: Option, - comments_v4: Option, - comments_v6: Option, - address: Option, + 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, bond_slaves: Option>, delete: Option>, digest: Option, + param: Value, ) -> Result<(), Error> { let _lock = crate::tools::open_file_locked(network::NETWORK_LOCKFILE, std::time::Duration::new(10, 0))?; @@ -218,71 +245,98 @@ pub fn update_interface( } let current_gateway_v4 = config.interfaces.iter() - .find(|(_, interface)| interface.gateway_v4.is_some()) + .find(|(_, interface)| interface.gateway.is_some()) .map(|(name, _)| name.to_string()); let current_gateway_v6 = config.interfaces.iter() - .find(|(_, interface)| interface.gateway_v4.is_some()) + .find(|(_, interface)| interface.gateway6.is_some()) .map(|(name, _)| name.to_string()); - let interface = config.lookup_mut(&name)?; + let interface = config.lookup_mut(&iface)?; + + if let Some(interface_type) = param.get("type") { + let interface_type: NetworkInterfaceType = serde_json::from_value(interface_type.clone())?; + 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::address_v4 => { interface.cidr_v4 = None; }, - DeletableProperty::address_v6 => { interface.cidr_v6 = None; }, - DeletableProperty::gateway_v4 => { interface.gateway_v4 = None; }, - DeletableProperty::gateway_v6 => { interface.gateway_v6 = None; }, - DeletableProperty::method_v4 => { interface.method_v4 = None; }, - DeletableProperty::method_v6 => { interface.method_v6 = None; }, - DeletableProperty::comments_v4 => { interface.comments_v4 = None; }, - DeletableProperty::comments_v6 => { interface.comments_v6 = None; }, + 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::auto => { interface.auto = false; }, + DeletableProperty::autostart => { interface.autostart = false; }, DeletableProperty::bridge_ports => { interface.set_bridge_ports(Vec::new())?; } + DeletableProperty::bridge_vlan_aware => { interface.bridge_vlan_aware = None; } DeletableProperty::bond_slaves => { interface.set_bond_slaves(Vec::new())?; } } } } - if let Some(auto) = auto { interface.auto = auto; } - if method_v4.is_some() { interface.method_v4 = method_v4; } - if method_v6.is_some() { interface.method_v6 = method_v6; } + 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 { interface.set_bridge_ports(ports)?; } + if bridge_vlan_aware.is_some() { interface.bridge_vlan_aware = bridge_vlan_aware; } if let Some(slaves) = bond_slaves { interface.set_bond_slaves(slaves)?; } - if let Some(address) = address { - let (_, _, is_v6) = network::parse_cidr(&address)?; - if is_v6 { - interface.cidr_v6 = Some(address); - } else { - interface.cidr_v4 = Some(address); - } + 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 { - if let Some(current_gateway_v6) = current_gateway_v6 { - if current_gateway_v6 != name { - bail!("Default IPv6 gateway already exists on interface '{}'", current_gateway_v6); - } + if is_v6 { bail!("invalid address type (expected IPv4, got IPv6)"); } + 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); } - interface.gateway_v6 = Some(gateway); - } else { - if let Some(current_gateway_v4) = current_gateway_v4 { - if current_gateway_v4 != name { - bail!("Default IPv4 gateway already exists on interface '{}'", current_gateway_v4); - } - } - interface.gateway_v4 = Some(gateway); } + interface.gateway = Some(gateway); } - if comments_v4.is_some() { interface.comments_v4 = comments_v4; } - if comments_v6.is_some() { interface.comments_v6 = comments_v6; } + if let Some(gateway6) = gateway6 { + let is_v6 = gateway6.contains(':'); + if !is_v6 { bail!("invalid address type (expected IPv6, got IPv4)"); } + 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); + } + } + 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 { + interface.method6 = Some(NetworkConfigMethod::Manual); + } network::save_config(&config)?; @@ -296,7 +350,7 @@ pub fn update_interface( node: { schema: NODE_SCHEMA, }, - name: { + iface: { schema: NETWORK_INTERFACE_NAME_SCHEMA, }, digest: { @@ -306,11 +360,11 @@ pub fn update_interface( }, }, access: { - permission: &Permission::Privilege(&["system", "network", "interfaces", "{name}"], PRIV_SYS_MODIFY, false), + permission: &Permission::Privilege(&["system", "network", "interfaces", "{iface}"], PRIV_SYS_MODIFY, false), }, )] /// Remove network interface configuration. -pub fn delete_interface(name: String, digest: Option) -> Result<(), Error> { +pub fn delete_interface(iface: String, digest: Option) -> Result<(), Error> { let _lock = crate::tools::open_file_locked(network::NETWORK_LOCKFILE, std::time::Duration::new(10, 0))?; @@ -321,9 +375,9 @@ pub fn delete_interface(name: String, digest: Option) -> Result<(), Erro crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?; } - let _interface = config.lookup(&name)?; // check if interface exists + let _interface = config.lookup(&iface)?; // check if interface exists - config.interfaces.remove(&name); + config.interfaces.remove(&iface); network::save_config(&config)?; @@ -331,6 +385,7 @@ pub fn delete_interface(name: String, digest: Option) -> Result<(), Erro } #[api( + protected: true, input: { properties: { node: { @@ -343,15 +398,23 @@ pub fn delete_interface(name: String, digest: Option) -> Result<(), Erro }, )] /// Reload network configuration (requires ifupdown2). -pub fn reload_network_config() -> Result<(), Error> { +pub async fn reload_network_config( + rpcenv: &mut dyn RpcEnvironment, +) -> Result { network::assert_ifupdown2_installed()?; - let _ = std::fs::rename(network::NETWORK_INTERFACES_NEW_FILENAME, network::NETWORK_INTERFACES_FILENAME); + let username = rpcenv.get_user().unwrap(); - network::network_reload()?; + let upid_str = WorkerTask::spawn("srvreload", Some(String::from("networking")), &username.clone(), true, |_worker| async { - Ok(()) + let _ = std::fs::rename(network::NETWORK_INTERFACES_NEW_FILENAME, network::NETWORK_INTERFACES_FILENAME); + + network::network_reload()?; + Ok(()) + })?; + + Ok(upid_str) } #[api( @@ -383,4 +446,4 @@ pub const ROUTER: Router = Router::new() .get(&API_METHOD_LIST_NETWORK_DEVICES) .put(&API_METHOD_RELOAD_NETWORK_CONFIG) .delete(&API_METHOD_REVERT_NETWORK_CONFIG) - .match_all("name", &ITEM_ROUTER); + .match_all("iface", &ITEM_ROUTER); diff --git a/src/api2/types.rs b/src/api2/types.rs index 62345d76..9278d946 100644 --- a/src/api2/types.rs +++ b/src/api2/types.rs @@ -557,9 +557,7 @@ pub enum NetworkInterfaceType { /// Loopback Loopback, /// Physical Ethernet device - Ethernet, - /// Name looks like a physical ethernet device, but device is not found - Vanished, + Eth, /// Linux Bridge Bridge, /// Linux Bond @@ -587,18 +585,34 @@ pub const NETWORK_INTERFACE_LIST_SCHEMA: Schema = ArraySchema::new( name: { schema: NETWORK_INTERFACE_NAME_SCHEMA, }, - interface_type: { + "type": { type: NetworkInterfaceType, }, - method_v4: { + method: { type: NetworkConfigMethod, optional: true, }, - method_v6: { + method6: { type: NetworkConfigMethod, optional: true, }, - options_v4: { + 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, + }, + options: { description: "Option list (inet)", type: Array, items: { @@ -606,7 +620,7 @@ pub const NETWORK_INTERFACE_LIST_SCHEMA: Schema = ArraySchema::new( type: String, }, }, - options_v6: { + options6: { description: "Option list (inet6)", type: Array, items: { @@ -614,12 +628,12 @@ pub const NETWORK_INTERFACE_LIST_SCHEMA: Schema = ArraySchema::new( type: String, }, }, - comments_v4: { + comments: { description: "Comments (inet, may span multiple lines)", type: String, optional: true, }, - comments_v6: { + comments6: { description: "Comments (inet6, may span multiple lines)", type: String, optional: true, @@ -638,39 +652,41 @@ pub const NETWORK_INTERFACE_LIST_SCHEMA: Schema = ArraySchema::new( /// Network Interface configuration pub struct Interface { /// Autostart interface - pub auto: bool, + #[serde(rename = "autostart")] + pub autostart: bool, /// Interface is active (UP) pub active: bool, /// Interface name pub name: String, /// Interface type + #[serde(rename = "type")] pub interface_type: NetworkInterfaceType, #[serde(skip_serializing_if="Option::is_none")] - pub method_v4: Option, + pub method: Option, #[serde(skip_serializing_if="Option::is_none")] - pub method_v6: Option, + pub method6: Option, #[serde(skip_serializing_if="Option::is_none")] /// IPv4 address with netmask - pub cidr_v4: Option, + pub cidr: Option, #[serde(skip_serializing_if="Option::is_none")] /// IPv4 gateway - pub gateway_v4: Option, + pub gateway: Option, #[serde(skip_serializing_if="Option::is_none")] /// IPv6 address with netmask - pub cidr_v6: Option, + pub cidr6: Option, #[serde(skip_serializing_if="Option::is_none")] /// IPv6 gateway - pub gateway_v6: Option, + pub gateway6: Option, #[serde(skip_serializing_if="Vec::is_empty")] - pub options_v4: Vec, + pub options: Vec, #[serde(skip_serializing_if="Vec::is_empty")] - pub options_v6: Vec, + pub options6: Vec, #[serde(skip_serializing_if="Option::is_none")] - pub comments_v4: Option, + pub comments: Option, #[serde(skip_serializing_if="Option::is_none")] - pub comments_v6: Option, + pub comments6: Option, #[serde(skip_serializing_if="Option::is_none")] /// Maximum Transmission Unit @@ -678,6 +694,9 @@ pub struct Interface { #[serde(skip_serializing_if="Option::is_none")] pub bridge_ports: Option>, + /// Enable bridge vlan support. + #[serde(skip_serializing_if="Option::is_none")] + pub bridge_vlan_aware: Option, #[serde(skip_serializing_if="Option::is_none")] pub bond_slaves: Option>, diff --git a/src/backup/catalog_shell.rs b/src/backup/catalog_shell.rs index 2f30ea2a..7683ed07 100644 --- a/src/backup/catalog_shell.rs +++ b/src/backup/catalog_shell.rs @@ -140,7 +140,9 @@ impl Shell { continue; } }; - let _ = handle_command(helper.cmd_def(), "", args, None); + + let rpcenv = CliEnvironment::new(); + let _ = handle_command(helper.cmd_def(), "", args, rpcenv, None); self.rl.add_history_entry(line); self.update_prompt()?; } diff --git a/src/bin/completion.rs b/src/bin/completion.rs index 6e5630bd..c1c3b69a 100644 --- a/src/bin/completion.rs +++ b/src/bin/completion.rs @@ -83,7 +83,8 @@ fn main() -> Result<(), Error> { let args = shellword_split(&line)?; - let _ = handle_command(helper.cmd_def(), "", args, None); + let rpcenv = CliEnvironment::new(); + let _ = handle_command(helper.cmd_def(), "", args, rpcenv, None); rl.add_history_entry(line); } diff --git a/src/bin/proxmox-backup-client.rs b/src/bin/proxmox-backup-client.rs index a4f2e9ea..4d6c0036 100644 --- a/src/bin/proxmox-backup-client.rs +++ b/src/bin/proxmox-backup-client.rs @@ -2421,7 +2421,8 @@ fn main() { .insert("catalog", catalog_mgmt_cli()) .insert("task", task_mgmt_cli()); - run_cli_command(cmd_def, Some(|future| { + let rpcenv = CliEnvironment::new(); + run_cli_command(cmd_def, rpcenv, Some(|future| { proxmox_backup::tools::runtime::main(future) })); } diff --git a/src/bin/proxmox-backup-manager.rs b/src/bin/proxmox-backup-manager.rs index 6c0cf63f..09d5b661 100644 --- a/src/bin/proxmox-backup-manager.rs +++ b/src/bin/proxmox-backup-manager.rs @@ -273,10 +273,10 @@ fn list_network_devices(mut param: Value, rpcenv: &mut dyn RpcEnvironment) -> Re fn render_address(_value: &Value, record: &Value) -> Result { let mut text = String::new(); - if let Some(cidr) = record["cidr_v4"].as_str() { + if let Some(cidr) = record["cidr"].as_str() { text.push_str(cidr); } - if let Some(cidr) = record["cidr_v6"].as_str() { + if let Some(cidr) = record["cidr6"].as_str() { if !text.is_empty() { text.push('\n'); } text.push_str(cidr); } @@ -287,10 +287,10 @@ fn list_network_devices(mut param: Value, rpcenv: &mut dyn RpcEnvironment) -> Re fn render_gateway(_value: &Value, record: &Value) -> Result { let mut text = String::new(); - if let Some(gateway) = record["gateway_v4"].as_str() { + if let Some(gateway) = record["gateway"].as_str() { text.push_str(gateway); } - if let Some(gateway) = record["gateway_v6"].as_str() { + if let Some(gateway) = record["gateway6"].as_str() { if !text.is_empty() { text.push('\n'); } text.push_str(gateway); } @@ -299,13 +299,13 @@ fn list_network_devices(mut param: Value, rpcenv: &mut dyn RpcEnvironment) -> Re } let options = default_table_format_options() - .column(ColumnConfig::new("interface_type").header("type")) + .column(ColumnConfig::new("type").header("type")) .column(ColumnConfig::new("name")) - .column(ColumnConfig::new("auto")) - .column(ColumnConfig::new("method_v4")) - .column(ColumnConfig::new("method_v6")) - .column(ColumnConfig::new("cidr_v4").header("address").renderer(render_address)) - .column(ColumnConfig::new("gateway_v4").header("gateway").renderer(render_gateway)); + .column(ColumnConfig::new("autostart")) + .column(ColumnConfig::new("method")) + .column(ColumnConfig::new("method6")) + .column(ColumnConfig::new("cidr").header("address").renderer(render_address)) + .column(ColumnConfig::new("gateway").header("gateway").renderer(render_gateway)); format_and_print_result_full(&mut data, info.returns, &output_format, &options); @@ -347,15 +347,15 @@ fn network_commands() -> CommandLineInterface { "update", CliCommand::new(&api2::node::network::API_METHOD_UPDATE_INTERFACE) .fixed_param("node", String::from("localhost")) - .arg_param(&["name"]) - .completion_cb("name", config::network::complete_interface_name) + .arg_param(&["iface"]) + .completion_cb("iface", config::network::complete_interface_name) ) .insert( "remove", CliCommand::new(&api2::node::network::API_METHOD_DELETE_INTERFACE) .fixed_param("node", String::from("localhost")) - .arg_param(&["name"]) - .completion_cb("name", config::network::complete_interface_name) + .arg_param(&["iface"]) + .completion_cb("iface", config::network::complete_interface_name) ) .insert( "revert", @@ -839,7 +839,10 @@ fn main() { .completion_cb("remote-store", complete_remote_datastore_name) ); - proxmox_backup::tools::runtime::main(run_async_cli_command(cmd_def)); + let mut rpcenv = CliEnvironment::new(); + rpcenv.set_user(Some(String::from("root@pam"))); + + proxmox_backup::tools::runtime::main(run_async_cli_command(cmd_def, rpcenv)); } // shell completion helper diff --git a/src/bin/pxar.rs b/src/bin/pxar.rs index 5d18b28d..5d1eb2e6 100644 --- a/src/bin/pxar.rs +++ b/src/bin/pxar.rs @@ -519,5 +519,6 @@ fn main() { .completion_cb("archive", tools::complete_file_name) ); - run_cli_command(cmd_def, None); + let rpcenv = CliEnvironment::new(); + run_cli_command(cmd_def, rpcenv, None); } diff --git a/src/config/network.rs b/src/config/network.rs index 1b739a37..ef8bf73a 100644 --- a/src/config/network.rs +++ b/src/config/network.rs @@ -22,27 +22,28 @@ impl Interface { Self { name, interface_type: NetworkInterfaceType::Unknown, - auto: false, + autostart: false, active: false, - method_v4: None, - method_v6: None, - cidr_v4: None, - gateway_v4: None, - cidr_v6: None, - gateway_v6: None, - options_v4: Vec::new(), - options_v6: Vec::new(), - comments_v4: None, - comments_v6: None, + method: None, + method6: None, + cidr: None, + gateway: None, + cidr6: None, + gateway6: None, + options: Vec::new(), + options6: Vec::new(), + comments: None, + comments6: None, mtu: None, bridge_ports: None, + bridge_vlan_aware: None, bond_slaves: None, } } fn set_method_v4(&mut self, method: NetworkConfigMethod) -> Result<(), Error> { - if self.method_v4.is_none() { - self.method_v4 = Some(method); + if self.method.is_none() { + self.method = Some(method); } else { bail!("inet configuration method already set."); } @@ -50,8 +51,8 @@ impl Interface { } fn set_method_v6(&mut self, method: NetworkConfigMethod) -> Result<(), Error> { - if self.method_v6.is_none() { - self.method_v6 = Some(method); + if self.method6.is_none() { + self.method6 = Some(method); } else { bail!("inet6 configuration method already set."); } @@ -59,8 +60,8 @@ impl Interface { } fn set_cidr_v4(&mut self, address: String) -> Result<(), Error> { - if self.cidr_v4.is_none() { - self.cidr_v4 = Some(address); + if self.cidr.is_none() { + self.cidr = Some(address); } else { bail!("duplicate IPv4 address."); } @@ -68,8 +69,8 @@ impl Interface { } fn set_gateway_v4(&mut self, gateway: String) -> Result<(), Error> { - if self.gateway_v4.is_none() { - self.gateway_v4 = Some(gateway); + if self.gateway.is_none() { + self.gateway = Some(gateway); } else { bail!("duplicate IPv4 gateway."); } @@ -77,8 +78,8 @@ impl Interface { } fn set_cidr_v6(&mut self, address: String) -> Result<(), Error> { - if self.cidr_v6.is_none() { - self.cidr_v6 = Some(address); + if self.cidr6.is_none() { + self.cidr6 = Some(address); } else { bail!("duplicate IPv6 address."); } @@ -86,8 +87,8 @@ impl Interface { } fn set_gateway_v6(&mut self, gateway: String) -> Result<(), Error> { - if self.gateway_v6.is_none() { - self.gateway_v6 = Some(gateway); + if self.gateway6.is_none() { + self.gateway6 = Some(gateway); } else { bail!("duplicate IPv4 gateway."); } @@ -124,20 +125,23 @@ impl Interface { match self.interface_type { NetworkInterfaceType::Bridge => { + if let Some(true) = self.bridge_vlan_aware { + writeln!(w, "\tbridge-vlan-aware yes")?; + } if let Some(ref ports) = self.bridge_ports { if ports.is_empty() { - writeln!(w, " bridge-ports none")?; + writeln!(w, "\tbridge-ports none")?; } else { - writeln!(w, " bridge-ports {}", ports.join(" "))?; + writeln!(w, "\tbridge-ports {}", ports.join(" "))?; } } } NetworkInterfaceType::Bond => { if let Some(ref slaves) = self.bond_slaves { if slaves.is_empty() { - writeln!(w, " bond-slaves none")?; + writeln!(w, "\tbond-slaves none")?; } else { - writeln!(w, " bond-slaves {}", slaves.join(" "))?; + writeln!(w, "\tbond-slaves {}", slaves.join(" "))?; } } } @@ -145,7 +149,7 @@ impl Interface { } if let Some(mtu) = self.mtu { - writeln!(w, " mtu {}", mtu)?; + writeln!(w, "\tmtu {}", mtu)?; } Ok(()) @@ -154,19 +158,19 @@ impl Interface { /// Write attributes dependening on address family inet (IPv4) fn write_iface_attributes_v4(&self, w: &mut dyn Write, method: NetworkConfigMethod) -> Result<(), Error> { if method == NetworkConfigMethod::Static { - if let Some(address) = &self.cidr_v4 { - writeln!(w, " address {}", address)?; + if let Some(address) = &self.cidr { + writeln!(w, "\taddress {}", address)?; } - if let Some(gateway) = &self.gateway_v4 { - writeln!(w, " gateway {}", gateway)?; + if let Some(gateway) = &self.gateway { + writeln!(w, "\tgateway {}", gateway)?; } } - for option in &self.options_v4 { - writeln!(w, " {}", option)?; + for option in &self.options { + writeln!(w, "\t{}", option)?; } - if let Some(ref comments) = self.comments_v4 { + if let Some(ref comments) = self.comments { for comment in comments.lines() { writeln!(w, "#{}", comment)?; } @@ -178,19 +182,19 @@ impl Interface { /// Write attributes dependening on address family inet6 (IPv6) fn write_iface_attributes_v6(&self, w: &mut dyn Write, method: NetworkConfigMethod) -> Result<(), Error> { if method == NetworkConfigMethod::Static { - if let Some(address) = &self.cidr_v6 { - writeln!(w, " address {}", address)?; + if let Some(address) = &self.cidr6 { + writeln!(w, "\taddress {}", address)?; } - if let Some(gateway) = &self.gateway_v6 { - writeln!(w, " gateway {}", gateway)?; + if let Some(gateway) = &self.gateway6 { + writeln!(w, "\tgateway {}", gateway)?; } } - for option in &self.options_v6 { - writeln!(w, " {}", option)?; + for option in &self.options6 { + writeln!(w, "\t{}", option)?; } - if let Some(ref comments) = self.comments_v6 { + if let Some(ref comments) = self.comments6 { for comment in comments.lines() { writeln!(w, "#{}", comment)?; } @@ -204,30 +208,31 @@ impl Interface { // Note: use match to make sure we considered all values at compile time match self { Interface { - method_v4, - method_v6, - options_v4, - options_v6, - comments_v4, - comments_v6, + method, + method6, + options, + options6, + comments, + comments6, // the rest does not matter name: _name, interface_type: _interface_type, - auto: _auto, + autostart: _autostart, active: _active, - cidr_v4: _cidr_v4, - cidr_v6: _cidr_v6, - gateway_v4: _gateway_v4, - gateway_v6: _gateway_v6, + cidr: _cidr, + cidr6: _cidr6, + gateway: _gateway, + gateway6: _gateway6, mtu: _mtu, bridge_ports: _bridge_ports, + bridge_vlan_aware: _bridge_vlan_aware, bond_slaves: _bond_slaves, } => { - method_v4 == method_v6 - && comments_v4.is_none() - && comments_v6.is_none() - && options_v4.is_empty() - && options_v6.is_empty() + method == method6 + && comments.is_none() + && comments6.is_none() + && options.is_empty() + && options6.is_empty() } } } @@ -243,36 +248,46 @@ impl Interface { } } - if self.method_v4.is_none() && self.method_v6.is_none() { return Ok(()); } + if self.method.is_none() && self.method6.is_none() { return Ok(()); } - if self.auto { + if self.autostart { writeln!(w, "auto {}", self.name)?; } if self.combine_entry() { - if let Some(method) = self.method_v4 { + if let Some(method) = self.method { writeln!(w, "iface {} {}", self.name, method_to_str(method))?; self.write_iface_attributes_v4(w, method)?; self.write_iface_attributes_v6(w, method)?; self.write_iface_attributes(w)?; writeln!(w)?; } - } else { - if let Some(method) = self.method_v4 { - writeln!(w, "iface {} inet {}", self.name, method_to_str(method))?; - self.write_iface_attributes_v4(w, method)?; - self.write_iface_attributes(w)?; - writeln!(w)?; + return Ok(()); + } + + if let Some(method) = self.method { + writeln!(w, "iface {} inet {}", self.name, method_to_str(method))?; + self.write_iface_attributes_v4(w, method)?; + self.write_iface_attributes(w)?; + writeln!(w)?; + } + + if let Some(method6) = self.method6 { + let mut skip_v6 = false; // avoid empty inet6 manual entry + if self.method.is_some() && method6 == NetworkConfigMethod::Manual { + if self.comments6.is_none() && self.options6.is_empty() { skip_v6 = true; } } - if let Some(method) = self.method_v6 { - writeln!(w, "iface {} inet6 {}", self.name, method_to_str(method))?; - self.write_iface_attributes_v6(w, method)?; - if self.method_v4.is_none() { // only write common attributes once + + if !skip_v6 { + writeln!(w, "iface {} inet6 {}", self.name, method_to_str(method6))?; + self.write_iface_attributes_v6(w, method6)?; + if self.method.is_none() { // only write common attributes once self.write_iface_attributes(w)?; } writeln!(w)?; } } + Ok(()) } } diff --git a/src/config/network/lexer.rs b/src/config/network/lexer.rs index 47ddb021..d8a769b2 100644 --- a/src/config/network/lexer.rs +++ b/src/config/network/lexer.rs @@ -23,6 +23,7 @@ pub enum Token { Attribute, MTU, BridgePorts, + BridgeVlanAware, BondSlaves, EOF, } @@ -44,6 +45,8 @@ lazy_static! { map.insert("mtu", Token::MTU); map.insert("bridge-ports", Token::BridgePorts); map.insert("bridge_ports", Token::BridgePorts); + map.insert("bridge-vlan-aware", Token::BridgeVlanAware); + map.insert("bridge_vlan_aware", Token::BridgeVlanAware); map.insert("bond-slaves", Token::BondSlaves); map.insert("bond_slaves", Token::BondSlaves); map diff --git a/src/config/network/parser.rs b/src/config/network/parser.rs index c4ab538a..f9f950b4 100644 --- a/src/config/network/parser.rs +++ b/src/config/network/parser.rs @@ -137,6 +137,21 @@ impl NetworkParser { Ok(mtu) } + fn parse_yes_no(&mut self) -> Result { + let text = self.next_text()?; + let value = match text.to_lowercase().as_str() { + "yes" => true, + "no" => false, + _ => { + bail!("unable to bool value '{}' - (expected yes/no)", text); + } + }; + + self.eat(Token::Newline)?; + + Ok(value) + } + fn parse_to_eol(&mut self) -> Result { let mut line = String::new(); loop { @@ -182,15 +197,15 @@ impl NetworkParser { Token::Comment => { let comment = self.eat(Token::Comment)?; if !address_family_v4 && address_family_v6 { - let mut comments = interface.comments_v6.take().unwrap_or(String::new()); + let mut comments = interface.comments6.take().unwrap_or(String::new()); if !comments.is_empty() { comments.push('\n'); } comments.push_str(&comment); - interface.comments_v6 = Some(comments); + interface.comments6 = Some(comments); } else { - let mut comments = interface.comments_v4.take().unwrap_or(String::new()); + let mut comments = interface.comments.take().unwrap_or(String::new()); if !comments.is_empty() { comments.push('\n'); } comments.push_str(&comment); - interface.comments_v4 = Some(comments); + interface.comments = Some(comments); } self.eat(Token::Newline)?; continue; @@ -207,6 +222,11 @@ impl NetworkParser { let mtu = self.parse_iface_mtu()?; interface.mtu = Some(mtu); } + Token::BridgeVlanAware => { + self.eat(Token::BridgeVlanAware)?; + let bridge_vlan_aware = self.parse_yes_no()?; + interface.bridge_vlan_aware = Some(bridge_vlan_aware); + } Token::BridgePorts => { self.eat(Token::BridgePorts)?; let ports = self.parse_iface_list()?; @@ -225,9 +245,9 @@ impl NetworkParser { let option = self.parse_to_eol()?; if !option.is_empty() { if !address_family_v4 && address_family_v6 { - interface.options_v6.push(option); + interface.options6.push(option); } else { - interface.options_v4.push(option); + interface.options.push(option); } }; }, @@ -335,7 +355,7 @@ impl NetworkParser { for iface in auto_flag.iter() { if let Some(interface) = config.interfaces.get_mut(iface) { - interface.auto = true; + interface.autostart = true; } } @@ -349,13 +369,13 @@ impl NetworkParser { for (iface, active) in existing_interfaces.iter() { if let Some(interface) = config.interfaces.get_mut(iface) { interface.active = *active; - if interface.interface_type == NetworkInterfaceType::Unknown { - interface.interface_type = NetworkInterfaceType::Ethernet; + if interface.interface_type == NetworkInterfaceType::Unknown && PHYSICAL_NIC_REGEX.is_match(iface) { + interface.interface_type = NetworkInterfaceType::Eth; } } else if PHYSICAL_NIC_REGEX.is_match(iface) { // also add all physical NICs let mut interface = Interface::new(iface.clone()); interface.set_method_v4(NetworkConfigMethod::Manual)?; - interface.interface_type = NetworkInterfaceType::Ethernet; + interface.interface_type = NetworkInterfaceType::Eth; interface.active = *active; config.interfaces.insert(interface.name.clone(), interface); config.order.push(NetworkOrderEntry::Iface(iface.to_string())); @@ -378,7 +398,7 @@ impl NetworkParser { continue; } if PHYSICAL_NIC_REGEX.is_match(name) { - interface.interface_type = NetworkInterfaceType::Vanished; + interface.interface_type = NetworkInterfaceType::Eth; continue; } } @@ -387,7 +407,7 @@ impl NetworkParser { let mut interface = Interface::new(String::from("lo")); interface.set_method_v4(NetworkConfigMethod::Loopback)?; interface.interface_type = NetworkInterfaceType::Loopback; - interface.auto = true; + interface.autostart = true; config.interfaces.insert(interface.name.clone(), interface); // Note: insert 'lo' as first interface after initial comments diff --git a/www/SystemConfiguration.js b/www/SystemConfiguration.js index 86a8b241..3d9e0f67 100644 --- a/www/SystemConfiguration.js +++ b/www/SystemConfiguration.js @@ -30,7 +30,8 @@ Ext.define('PBS.SystemConfiguration', { minHeight: 200, title: gettext('Interfaces'), xtype: 'proxmoxNodeNetworkView', - types: ['bond'], + showApplyBtn: true, + types: ['bond', 'bridge', 'vlan'], nodename: 'localhost' }, {