From b276acf7bae811dd49cbeda366427dc41e8f1b81 Mon Sep 17 00:00:00 2001 From: Stefan Lendl Date: Thu, 4 Apr 2024 12:00:32 +0200 Subject: [PATCH] config: parse vlan interface from config Support three types of vlan configurations defined in interfaces, conforming to the PVE configurations: iface nic. inet iface vlan inet vlan-raw-device iface inet vlan-id vlan-raw-device * Add lexer Token enum variants for vlan-id and vlan-raw-device and parse them in parse_iface_attributes. * Add tests to verify this works in the above scenarios Signed-off-by: Stefan Lendl Tested-by: Lukas Wagner Reviewed-by: Lukas Wagner Tested-by: Folke Gleumes Signed-off-by: Thomas Lamprecht --- pbs-config/src/network/lexer.rs | 6 ++ pbs-config/src/network/parser.rs | 97 +++++++++++++++++++++++++++++++- 2 files changed, 102 insertions(+), 1 deletion(-) diff --git a/pbs-config/src/network/lexer.rs b/pbs-config/src/network/lexer.rs index fd23e3d8..d0b7d8cd 100644 --- a/pbs-config/src/network/lexer.rs +++ b/pbs-config/src/network/lexer.rs @@ -24,6 +24,8 @@ pub enum Token { MTU, BridgePorts, BridgeVlanAware, + VlanId, + VlanRawDevice, BondSlaves, BondMode, BondPrimary, @@ -50,6 +52,10 @@ lazy_static! { map.insert("bridge_ports", Token::BridgePorts); map.insert("bridge-vlan-aware", Token::BridgeVlanAware); map.insert("bridge_vlan_aware", Token::BridgeVlanAware); + map.insert("vlan-id", Token::VlanId); + map.insert("vlan_id", Token::VlanId); + map.insert("vlan-raw-device", Token::VlanRawDevice); + map.insert("vlan_raw_device", Token::VlanRawDevice); map.insert("bond-slaves", Token::BondSlaves); map.insert("bond_slaves", Token::BondSlaves); map.insert("bond-mode", Token::BondMode); diff --git a/pbs-config/src/network/parser.rs b/pbs-config/src/network/parser.rs index 1b55569a..796e9308 100644 --- a/pbs-config/src/network/parser.rs +++ b/pbs-config/src/network/parser.rs @@ -361,6 +361,20 @@ impl NetworkParser { interface.bond_xmit_hash_policy = Some(policy); self.eat(Token::Newline)?; } + Token::VlanId => { + self.eat(Token::VlanId)?; + let vlan_id = self.next_text()?.parse()?; + interface.vlan_id = Some(vlan_id); + set_interface_type(interface, NetworkInterfaceType::Vlan)?; + self.eat(Token::Newline)?; + } + Token::VlanRawDevice => { + self.eat(Token::VlanRawDevice)?; + let vlan_raw_device = self.next_text()?; + interface.vlan_raw_device = Some(vlan_raw_device); + set_interface_type(interface, NetworkInterfaceType::Vlan)?; + self.eat(Token::Newline)?; + } _ => { // parse addon attributes let option = self.parse_to_eol()?; @@ -522,7 +536,7 @@ impl NetworkParser { lazy_static! { static ref INTERFACE_ALIAS_REGEX: Regex = Regex::new(r"^\S+:\d+$").unwrap(); - static ref VLAN_INTERFACE_REGEX: Regex = Regex::new(r"^\S+\.\d+$").unwrap(); + static ref VLAN_INTERFACE_REGEX: Regex = Regex::new(r"^\S+\.\d+|vlan\d+$").unwrap(); } if let Some(existing_interfaces) = existing_interfaces { @@ -748,4 +762,85 @@ mod test { Ok(()) } + + #[test] + fn test_network_config_parser_vlan_id_in_name() { + let input = "iface vmbr0.100 inet static manual"; + let mut parser = NetworkParser::new(input.as_bytes()); + let config = parser.parse_interfaces(None).unwrap(); + + let iface = config.interfaces.get("vmbr0.100").unwrap(); + assert_eq!(iface.interface_type, NetworkInterfaceType::Vlan); + assert_eq!(iface.vlan_raw_device, None); + assert_eq!(iface.vlan_id, None); + } + + #[test] + fn test_network_config_parser_vlan_with_raw_device() { + let input = r#" +iface vlan100 inet manual + vlan-raw-device vmbr0"#; + + let mut parser = NetworkParser::new(input.as_bytes()); + let config = parser.parse_interfaces(None).unwrap(); + + let iface = config.interfaces.get("vlan100").unwrap(); + assert_eq!(iface.interface_type, NetworkInterfaceType::Vlan); + assert_eq!(iface.vlan_raw_device, Some(String::from("vmbr0"))); + assert_eq!(iface.vlan_id, None); + } + + #[test] + fn test_network_config_parser_vlan_with_raw_device_static() { + let input = r#" +iface vlan100 inet static + vlan-raw-device vmbr0 + address 10.0.0.100/16"#; + + let mut parser = NetworkParser::new(input.as_bytes()); + let config = parser.parse_interfaces(None).unwrap(); + + let iface = config.interfaces.get("vlan100").unwrap(); + assert_eq!(iface.interface_type, NetworkInterfaceType::Vlan); + assert_eq!(iface.vlan_raw_device, Some(String::from("vmbr0"))); + assert_eq!(iface.vlan_id, None); + assert_eq!(iface.method, Some(NetworkConfigMethod::Static)); + assert_eq!(iface.cidr, Some(String::from("10.0.0.100/16"))); + } + + #[test] + fn test_network_config_parser_vlan_individual_name() { + let input = r#" +iface individual_name inet manual + vlan-id 100 + vlan-raw-device vmbr0"#; + + let mut parser = NetworkParser::new(input.as_bytes()); + let config = parser.parse_interfaces(None).unwrap(); + + let iface = config.interfaces.get("individual_name").unwrap(); + assert_eq!(iface.interface_type, NetworkInterfaceType::Vlan); + assert_eq!(iface.vlan_raw_device, Some(String::from("vmbr0"))); + assert_eq!(iface.vlan_id, Some(100)); + } + + #[test] + fn test_network_config_parser_vlan_individual_name_static() { + let input = r#" +iface individual_name inet static + vlan-id 100 + vlan-raw-device vmbr0 + address 10.0.0.100/16 +"#; + + let mut parser = NetworkParser::new(input.as_bytes()); + let config = parser.parse_interfaces(None).unwrap(); + + let iface = config.interfaces.get("individual_name").unwrap(); + assert_eq!(iface.interface_type, NetworkInterfaceType::Vlan); + assert_eq!(iface.vlan_raw_device, Some(String::from("vmbr0"))); + assert_eq!(iface.vlan_id, Some(100)); + assert_eq!(iface.method, Some(NetworkConfigMethod::Static)); + assert_eq!(iface.cidr, Some(String::from("10.0.0.100/16"))); + } }