diff --git a/harmony-rs/Cargo.lock b/harmony-rs/Cargo.lock index 57e2bb8..59198b1 100644 --- a/harmony-rs/Cargo.lock +++ b/harmony-rs/Cargo.lock @@ -1248,12 +1248,26 @@ dependencies = [ "async-trait", "env_logger", "log", + "opnsense-config-xml", "pretty_assertions", "russh", "russh-keys", "serde", "thiserror", "tokio", +] + +[[package]] +name = "opnsense-config-xml" +version = "0.1.0" +dependencies = [ + "async-trait", + "env_logger", + "log", + "pretty_assertions", + "serde", + "thiserror", + "tokio", "xml-rs", "yaserde", "yaserde_derive", diff --git a/harmony-rs/Cargo.toml b/harmony-rs/Cargo.toml index 89dbb19..bd5e6b8 100644 --- a/harmony-rs/Cargo.toml +++ b/harmony-rs/Cargo.toml @@ -3,7 +3,7 @@ resolver = "2" members = [ "private_repos/*", "harmony", - "opnsense-config", + "opnsense-config", "opnsense-config-xml", ] [workspace.package] diff --git a/harmony-rs/opnsense-config-xml/Cargo.toml b/harmony-rs/opnsense-config-xml/Cargo.toml new file mode 100644 index 0000000..a193f0c --- /dev/null +++ b/harmony-rs/opnsense-config-xml/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "opnsense-config-xml" +edition = "2021" +version.workspace = true +readme.workspace = true +license.workspace = true + +[dependencies] +serde = { version = "1.0.123", features = [ "derive" ] } +log = { workspace = true } +env_logger = { workspace = true } +#yaserde = { git = "https://git.nationtech.io/NationTech/yaserde" } +#yaserde_derive = { git = "https://git.nationtech.io/NationTech/yaserde" } +yaserde = { path = "../../../../github/yaserde/yaserde" } +yaserde_derive = { path = "../../../../github/yaserde/yaserde_derive" } +xml-rs = "0.8" +thiserror = "1.0" +async-trait = { workspace = true } +tokio = { workspace = true } + +[dev-dependencies] +pretty_assertions = "1.4.1" diff --git a/harmony-rs/opnsense-config-xml/src/data/dhcpd.rs b/harmony-rs/opnsense-config-xml/src/data/dhcpd.rs new file mode 100644 index 0000000..18bf272 --- /dev/null +++ b/harmony-rs/opnsense-config-xml/src/data/dhcpd.rs @@ -0,0 +1,116 @@ +use yaserde_derive::{YaDeserialize, YaSerialize}; + +use crate::xml_utils::MaybeString; + +use super::opnsense::{NumberOption, Range, StaticMap}; + +#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] +#[yaserde(rename = "dhcpd")] +pub struct Dhcpd { + #[yaserde(rename = "lan")] + pub lan: DhcpInterface, +} + +#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] +pub struct DhcpInterface { + pub enable: i32, + pub gateway: String, + pub domain: String, + #[yaserde(rename = "ddnsdomainalgorithm")] + pub ddns_domain_algorithm: String, + #[yaserde(rename = "numberoptions")] + pub number_options: Vec, + #[yaserde(rename = "range")] + pub range: Range, + pub winsserver: MaybeString, + pub dnsserver: MaybeString, + pub ntpserver: MaybeString, + #[yaserde(rename = "staticmap")] + pub staticmaps: Vec, + pub pool: MaybeString, +} + +#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] +pub struct DhcpRange { + #[yaserde(rename = "from")] + pub from: String, + #[yaserde(rename = "to")] + pub to: String, +} + +#[cfg(test)] +mod test { + use std::net::Ipv4Addr; + + use crate::xml_utils::to_xml_str; + + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn dhcpd_should_deserialize_serialize_identical() { + let dhcpd: Dhcpd = + yaserde::de::from_str(SERIALIZED_DHCPD).expect("Deserialize Dhcpd failed"); + + assert_eq!( + to_xml_str(&dhcpd).expect("Serialize Dhcpd failed"), + SERIALIZED_DHCPD + ); + } + + const SERIALIZED_DHCPD: &str = " + + + 1 + 192.168.20.1 + somedomain.yourlocal.mcd + hmac-md5 + + + + + 192.168.20.50 + 192.168.20.200 + + + 192.168.20.1 + + + 55:55:55:55:55:1c + 192.168.20.160 + somehost983 + someservire8 + + + + + + 55:55:55:55:55:1c + 192.168.20.155 + somehost893 + + + + + + 55:55:55:55:55:1c + 192.168.20.165 + somehost893 + + + + + + + 55:55:55:55:55:1c + 192.168.20.50 + hostswitch2 + switch-2 (bottom) + + + + + + +\n"; +} diff --git a/harmony-rs/opnsense-config-xml/src/data/interfaces.rs b/harmony-rs/opnsense-config-xml/src/data/interfaces.rs new file mode 100644 index 0000000..42e37b1 --- /dev/null +++ b/harmony-rs/opnsense-config-xml/src/data/interfaces.rs @@ -0,0 +1,229 @@ +use xml::reader::XmlEvent; +use yaserde::{YaDeserialize as YaDeserializeTrait, YaSerialize as YaSerializeTrait}; +use yaserde_derive::{YaDeserialize, YaSerialize}; + +use std::collections::HashMap; + +use crate::xml_utils::MaybeString; + +#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] +pub struct Interfaces { + pub interfaces: HashMap, +} + +#[derive(Default, PartialEq, Debug, YaDeserialize, YaSerialize)] +pub struct Interface { + #[yaserde(rename = "if")] + pub physical_interface_name: String, + pub descr: String, + pub enable: MaybeString, + #[yaserde(rename = "spoofmac")] + pub spoof_mac: Option, + pub internal_dynamic: Option, + pub ipaddr: Option, + #[yaserde(rename = "blockpriv")] + pub block_priv: Option, + #[yaserde(rename = "blockbogons")] + pub block_bogons: Option, + pub lock: Option, + #[yaserde(rename = "type")] + pub r#type: Option, + #[yaserde(rename = "virtual")] + pub r#virtual: Option, + pub subnet: Option, + pub networks: Option, + pub subnetv6: Option, + pub ipaddrv6: Option, + #[yaserde(rename = "track6-interface")] + pub track6_interface: Option, + #[yaserde(rename = "track6-prefix-id")] + pub track6_prefix_id: Option, +} + +pub trait MyDeserialize : YaDeserializeTrait {} +pub trait MySerialize : YaSerializeTrait {} + + +#[cfg(test)] +mod test { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn should_deserialize_interfaces() { + let interfaces = yaserde::de::from_str::(FULL_INTERFACES_XML).unwrap(); + assert_eq!(interfaces.interfaces.len(), 6) + + } + + const FULL_INTERFACES_XML: &str = " + + pppoe0 + WAN + 1 + 1 + + 1 + 1 + pppoe + + + em1 + LAN + 1 + + 192.168.20.1 + 24 + track6 + + 0 + + + 1 + Loopback + 1 + lo0 + 127.0.0.1 + ::1 + 8 + 128 + none + 1 + + + em5 + backup_sync + 1 + 1 + + 10.10.5.1 + 24 + + + 1 + WireGuard (Group) + wireguard + 1 + 1 + group + + + + 1 + 1 + openvpn + OpenVPN + group + 1 + + + "; +} + + +// impl YaSerializeTrait for Interfaces { +// fn serialize( +// &self, +// writer: &mut yaserde::ser::Serializer, +// ) -> Result<(), String> { +// writer.write("Interfaces serializer TODO").map_err(|e| e.to_string()) +// } +// +// fn serialize_attributes( +// &self, +// attributes: Vec, +// namespace: xml::namespace::Namespace, +// ) -> Result< +// ( +// Vec, +// xml::namespace::Namespace, +// ), +// String, +// > { +// todo!() +// } +// } +// +// impl YaDeserializeTrait for Interfaces { +// fn deserialize( +// reader: &mut yaserde::de::Deserializer, +// ) -> Result { +// let mut interfaces = Interfaces::default(); +// let mut current_interface_name = String::new(); +// let mut current_interface = Interface::default(); +// +// let mut event = reader.next_event()?; +// loop { +// match event { +// xml::reader::XmlEvent::EndElement { name } => { +// println!( +// "Handling EndElement {}, current_interface_name: {}", +// name.local_name, current_interface_name +// ); +// println!("Peeking after EndElement {:?}", reader.peek()?); +// if name.local_name == "interfaces" { +// break; +// } +// todo!("Should not hit here"); +// } +// xml::reader::XmlEvent::StartElement { ref name, .. } => { +// println!("Got start Element {:?}", name); +// current_interface_name = name.local_name.clone(); +// println!("About to deserialize interface from name {:?}", name); +// // TODO store names +// 'inner_iface: loop { +// let peek = reader.peek()?; +// println!("Peeking before forcing next event {:?}", peek); +// +// if let XmlEvent::EndElement { name } = peek { +// // TODO save the interface name and struct in the hashmap +// println!("Forcing next_event"); +// reader.next_event()?; +// +// let peek = reader.peek()?; +// println!("Peeking after next event deserializing {:?}", peek); +// if let XmlEvent::EndElement { name } = peek { +// println!("Got two EndElement in a row, breaking out of loop"); +// break 'inner_iface; +// } +// } +// println!("Peeking before deserializing {:?}", reader.peek()?); +// let interface = ::deserialize(reader)?; +// println!("Interface deserialized {:?}", interface); +// println!("Peeking after deserializing {:?}", reader.peek()?); +// } +// println!( +// "Done with inner interface, loop completed {:?}", +// reader.peek()? +// ); +// } +// xml::reader::XmlEvent::EndDocument => break, +// _ => { +// return Err( +// "This Interfaces Deserializer does not support all XmlEvent types".into(), +// ) +// } +// } +// let peek = reader.peek()?; +// +// let read_next = true; +// if let XmlEvent::EndElement { name } = peek { +// if name.local_name == "interfaces" { +// println!("Got endElement interfaces, not reading next event"); +// break; +// } +// } +// +// if read_next { +// event = reader.next_event()?; +// } +// println!("Outer loop got event {:?}", event); +// println!("Outer loop got peek {:?}", reader.peek()?); +// } +// println!("Done with interfaces {:?}", interfaces); +// println!("reader peeking shows {:?}", reader.peek()); +// +// Ok(interfaces) +// } +// } + diff --git a/harmony-rs/opnsense-config-xml/src/data/mod.rs b/harmony-rs/opnsense-config-xml/src/data/mod.rs new file mode 100644 index 0000000..5d2a4da --- /dev/null +++ b/harmony-rs/opnsense-config-xml/src/data/mod.rs @@ -0,0 +1,6 @@ +mod opnsense; +mod interfaces; +mod dhcpd; +pub use opnsense::*; +pub use interfaces::*; +pub use dhcpd::*; diff --git a/harmony-rs/opnsense-config/src/modules/opnsense.rs b/harmony-rs/opnsense-config-xml/src/data/opnsense.rs similarity index 83% rename from harmony-rs/opnsense-config/src/modules/opnsense.rs rename to harmony-rs/opnsense-config-xml/src/data/opnsense.rs index 6ed6df4..203831b 100644 --- a/harmony-rs/opnsense-config/src/modules/opnsense.rs +++ b/harmony-rs/opnsense-config-xml/src/data/opnsense.rs @@ -1,7 +1,24 @@ -use super::dhcp::Dhcpd; -use crate::infra::{generic_xml::RawXml, maybe_string::MaybeString}; +use crate::data::dhcpd::Dhcpd; +use crate::xml_utils::{MaybeString, RawXml}; +use log::error; use yaserde_derive::{YaDeserialize, YaSerialize}; +impl From for OPNsense { + fn from(content: String) -> Self { + yaserde::de::from_str(&content) + .map_err(|e| error!("{}", e.to_string())) + .expect("OPNSense received invalid string, should be full XML") + } +} + +impl OPNsense { + pub fn to_xml(&self) -> String { + yaserde::ser::to_string(self) + .map_err(|e| error!("{}", e.to_string())) + .expect("OPNSense could not serialize to XML") + } +} + #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] #[yaserde(rename = "opnsense")] pub struct OPNsense { @@ -19,7 +36,7 @@ pub struct OPNsense { pub widgets: Widgets, pub revision: Revision, #[yaserde(rename = "OPNsense")] - pub opnsense: OPNsenseConfig, + pub opnsense: OPNsenseXmlSection, pub staticroutes: StaticRoutes, pub ca: MaybeString, pub gateways: Gateways, @@ -279,42 +296,6 @@ pub struct WebGui { pub compression: MaybeString, } -use std::collections::HashMap; - -#[derive(Default, PartialEq, Debug)] -pub struct Interfaces { - pub interfaces: HashMap, -} - -#[derive(Default, PartialEq, Debug, YaDeserialize, YaSerialize)] -pub struct Interface { - #[yaserde(rename = "if")] - pub physical_interface_name: String, - pub descr: String, - pub enable: u8, - #[yaserde(rename = "spoofmac")] - pub spoof_mac: MaybeString, - pub internal_dynamic: Option, - pub ipaddr: MaybeString, - #[yaserde(rename = "blockpriv")] - pub block_priv: Option, - #[yaserde(rename = "blockbogons")] - pub block_bogons: Option, - pub lock: Option, - #[yaserde(rename = "type")] - pub r#type: MaybeString, - #[yaserde(rename = "virtual")] - pub r#virtual: MaybeString, - pub subnet: MaybeString, - pub networks: Option, - pub subnetv6: MaybeString, - pub ipaddrv6: MaybeString, - #[yaserde(rename = "track6-interface")] - pub track6_interface: MaybeString, - #[yaserde(rename = "track6-prefix-id")] - pub track6_prefix_id: MaybeString, -} - #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] pub struct NumberOption { item: MaybeString, @@ -406,7 +387,7 @@ pub struct Created { #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] #[yaserde(rename = "OPNsense")] -pub struct OPNsenseConfig { +pub struct OPNsenseXmlSection { pub captiveportal: Option, pub cron: Option, #[yaserde(rename = "Netflow")] @@ -463,33 +444,41 @@ pub struct IDSGeneral { promisc: Option, interfaces: String, homenet: String, - defaultPacketSize: MaybeString, - UpdateCron: MaybeString, - AlertLogrotate: String, - AlertSaveLogs: u8, - MPMAlgo: String, + #[yaserde(rename = "defaultPacketSize")] + default_packet_size: MaybeString, + #[yaserde(rename = "UpdateCron")] + update_cron: MaybeString, + #[yaserde(rename = "AlertLogrotate")] + alert_logrotate: String, + #[yaserde(rename = "AlertSaveLogs")] + alert_save_logs: u8, + #[yaserde(rename = "MPMAlgo")] + mpm_algo: String, detect: Detect, syslog: Option, syslog_eve: Option, - LogPayload: Option, + #[yaserde(rename = "LogPayload")] + log_payload: Option, verbosity: MaybeString, } #[derive(Debug, YaSerialize, YaDeserialize, PartialEq)] pub struct Detect { - Profile: String, + #[yaserde(rename = "Profile")] + profile: String, toclient_groups: MaybeString, toserver_groups: MaybeString, } #[derive(Debug, YaSerialize, YaDeserialize, PartialEq)] -#[yaserde(rename = "IPsec")] pub struct IPsec { #[yaserde(attribute)] version: String, general: GeneralIpsec, - keyPairs: MaybeString, - preSharedKeys: MaybeString, + #[yaserde(rename = "keyPairs")] + key_pairs: MaybeString, + #[yaserde(rename = "preSharedKeys")] + pre_shared_keys: MaybeString, } #[derive(Debug, YaSerialize, YaDeserialize, PartialEq)] @@ -891,10 +880,14 @@ pub struct LocalCache { #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] pub struct Traffic { pub enabled: i32, - pub maxDownloadSize: i32, - pub maxUploadSize: i32, - pub OverallBandwidthTrotteling: i32, - pub perHostTrotteling: i32, + #[yaserde(rename = "maxDownloadSize")] + pub max_download_size: i32, + #[yaserde(rename = "maxUploadSize")] + pub max_upload_size: i32, + #[yaserde(rename = "OverallBandwidthTrotteling")] + pub overall_bandwidth_trotteling: i32, + #[yaserde(rename = "perHostTrotteling")] + pub per_host_trotteling: i32, } #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] @@ -923,11 +916,16 @@ pub struct Forward { pub snmp_enable: i32, pub snmp_port: i32, pub snmp_password: String, - pub ftpInterfaces: MaybeString, - pub ftpPort: i32, - pub ftpTransparentMode: i32, - pub addACLforInterfaceSubnets: i32, - pub transparentMode: i32, + #[yaserde(rename = "ftpInterfaces")] + pub ftp_interfaces: MaybeString, + #[yaserde(rename = "ftpPort")] + pub ftp_port: i32, + #[yaserde(rename = "ftpTransparentMode")] + pub ftp_transparent_mode: i32, + #[yaserde(rename = "addACLforInterfaceSubnets")] + pub add_acl_for_interface_subnets: i32, + #[yaserde(rename = "transparentMode")] + pub transparent_mode: i32, pub acl: Acl, pub icap: Icap, pub authentication: Authentication, @@ -935,47 +933,66 @@ pub struct Forward { #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] pub struct Acl { - pub allowedSubnets: MaybeString, + #[yaserde(rename = "allowedSubnets")] + pub allowed_subnets: MaybeString, pub unrestricted: MaybeString, - pub bannedHosts: MaybeString, - pub whiteList: MaybeString, - pub blackList: MaybeString, + #[yaserde(rename = "bannedHosts")] + pub banned_hosts: MaybeString, + #[yaserde(rename = "whiteList")] + pub white_list: MaybeString, + #[yaserde(rename = "blackList")] + pub black_list: MaybeString, pub browser: MaybeString, - pub mimeType: MaybeString, - pub googleapps: MaybeString, + #[yaserde(rename = "mimeType")] + pub mime_type: MaybeString, + pub google_apps: MaybeString, pub youtube: MaybeString, - pub safePorts: String, - pub sslPorts: String, - pub remoteACLs: RemoteAcls, + #[yaserde(rename = "safePorts")] + pub safe_ports: String, + #[yaserde(rename = "sslPorts")] + pub ssl_ports: String, + #[yaserde(rename = "remoteACLs")] + pub remote_acls: RemoteAcls, } #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] pub struct RemoteAcls { pub blacklists: MaybeString, - pub UpdateCron: MaybeString, + #[yaserde(rename = "UpdateCron")] + pub update_cron: MaybeString, } #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] pub struct Icap { pub enable: i32, - pub RequestURL: String, - pub ResponseURL: String, - pub SendClientIP: i32, - pub SendUsername: i32, - pub EncodeUsername: i32, - pub UsernameHeader: String, - pub EnablePreview: i32, - pub PreviewSize: i32, - pub OptionsTTL: i32, + #[yaserde(rename = "RequestURL")] + pub request_url: String, + #[yaserde(rename = "ResponseURL")] + pub response_url: String, + #[yaserde(rename = "SendClientIP")] + pub send_client_ip: i32, + #[yaserde(rename = "SendUsername")] + pub send_username: i32, + #[yaserde(rename = "EncodeUsername")] + pub encode_username: i32, + #[yaserde(rename = "UsernameHeader")] + pub username_header: String, + #[yaserde(rename = "EnablePreview")] + pub enable_preview: i32, + #[yaserde(rename = "PreviewSize")] + pub preview_size: i32, + #[yaserde(rename = "OptionsTTL")] + pub options_ttl: i32, pub exclude: MaybeString, } #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] pub struct Authentication { pub method: MaybeString, - pub authEnforceGroup: MaybeString, + #[yaserde(rename = "authEnforceGroup")] + pub auth_enforce_group: MaybeString, pub realm: String, - pub credentialsttl: i32, + pub credentialsttl: i32, // This field is already in snake_case pub children: i32, } @@ -1263,8 +1280,11 @@ pub struct Account { pub struct ConfigOpenVPN { #[yaserde(attribute)] pub version: String, + #[yaserde(rename = "Overwrites")] pub Overwrites: MaybeString, + #[yaserde(rename = "Instances")] pub Instances: MaybeString, + #[yaserde(rename = "StaticKeys")] pub StaticKeys: MaybeString, } @@ -1327,12 +1347,18 @@ pub struct CronJobs { #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] pub struct HaProxyGeneral { pub enabled: i32, - pub gracefulStop: i32, - pub hardStopAfter: String, - pub closeSpreadTime: MaybeString, - pub seamlessReload: i32, - pub storeOcsp: i32, - pub showIntro: i32, + #[yaserde(rename = "gracefulStop")] + pub graceful_stop: i32, + #[yaserde(rename = "hardStopAfter")] + pub hard_stop_after: String, + #[yaserde(rename = "closeSpreadTime")] + pub close_spread_time: MaybeString, + #[yaserde(rename = "seamlessReload")] + pub seamless_reload: i32, + #[yaserde(rename = "storeOcsp")] + pub store_ocsp: i32, + #[yaserde(rename = "showIntro")] + pub show_intro: i32, pub peers: Peers, pub tuning: Tuning, pub defaults: HaProxyDefaults, @@ -1355,36 +1381,56 @@ pub struct Peers { #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] pub struct Tuning { pub root: i32, - pub maxConnections: MaybeString, + #[yaserde(rename = "maxConnections")] + pub max_connections: MaybeString, pub nbthread: i32, - pub sslServerVerify: String, - pub maxDHSize: i32, - pub bufferSize: i32, - pub spreadChecks: i32, - pub bogusProxyEnabled: i32, - pub luaMaxMem: i32, - pub customOptions: MaybeString, + #[yaserde(rename = "sslServerVerify")] + pub ssl_server_verify: String, + #[yaserde(rename = "maxDHSize")] + pub max_dh_size: i32, + #[yaserde(rename = "bufferSize")] + pub buffer_size: i32, + #[yaserde(rename = "spreadChecks")] + pub spread_checks: i32, + #[yaserde(rename = "bogusProxyEnabled")] + pub bogus_proxy_enabled: i32, + #[yaserde(rename = "luaMaxMem")] + pub lua_max_mem: i32, + #[yaserde(rename = "customOptions")] + pub custom_options: MaybeString, #[yaserde(rename = "ssl_defaultsEnabled")] pub ssl_defaults_enabled: i32, - pub ssl_bindOptions: String, - pub ssl_minVersion: String, - pub ssl_maxVersion: MaybeString, - pub ssl_cipherList: String, - pub ssl_cipherSuites: String, + #[yaserde(rename = "ssl_bindOptions")] + pub ssl_bind_options: String, + #[yaserde(rename = "ssl_minVersion")] + pub ssl_min_version: String, + #[yaserde(rename = "ssl_maxVersion")] + pub ssl_max_version: MaybeString, + #[yaserde(rename = "ssl_cipherList")] + pub ssl_cipher_list: String, + #[yaserde(rename = "ssl_cipherSuites")] + pub ssl_cipher_suites: String, } #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] pub struct HaProxyDefaults { - pub maxConnections: MaybeString, - pub maxConnectionsServers: MaybeString, - pub timeoutClient: String, - pub timeoutConnect: String, - pub timeoutCheck: MaybeString, - pub timeoutServer: String, + #[yaserde(rename = "maxConnections")] + pub max_connections: MaybeString, + #[yaserde(rename = "maxConnectionsServers")] + pub max_connections_servers: MaybeString, + #[yaserde(rename = "timeoutClient")] + pub timeout_client: String, + #[yaserde(rename = "timeoutConnect")] + pub timeout_connect: String, + #[yaserde(rename = "timeoutCheck")] + pub timeout_check: MaybeString, + #[yaserde(rename = "timeoutServer")] + pub timeout_server: String, pub retries: i32, pub redispatch: String, pub init_addr: String, - pub customOptions: MaybeString, + #[yaserde(rename = "customOptions")] + pub custom_options: MaybeString, } #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] @@ -1399,13 +1445,20 @@ pub struct HaProxyLogging { pub struct Stats { pub enabled: i32, pub port: i32, - pub remoteEnabled: i32, - pub remoteBind: MaybeString, - pub authEnabled: i32, + #[yaserde(rename = "remoteEnabled")] + pub remote_enabled: i32, + #[yaserde(rename = "remoteBind")] + pub remote_bind: MaybeString, + #[yaserde(rename = "authEnabled")] + pub auth_enabled: i32, + #[yaserde(rename = "users")] pub users: MaybeString, - pub allowedUsers: MaybeString, - pub allowedGroups: MaybeString, - pub customOptions: MaybeString, + #[yaserde(rename = "allowedUsers")] + pub allowed_users: MaybeString, + #[yaserde(rename = "allowedGroups")] + pub allowed_groups: MaybeString, + #[yaserde(rename = "customOptions")] + pub custom_options: MaybeString, pub prometheus_enabled: i32, pub prometheus_bind: String, pub prometheus_path: String, @@ -1414,16 +1467,20 @@ pub struct Stats { #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] pub struct HaProxyCache { pub enabled: i32, - pub totalMaxSize: i32, - pub maxAge: i32, - pub maxObjectSize: MaybeString, - pub processVary: i32, - pub maxSecondaryEntries: i32, + #[yaserde(rename = "totalMaxSize")] + pub total_max_size: i32, + #[yaserde(rename = "maxAge")] + pub max_age: i32, + #[yaserde(rename = "maxObjectSize")] + pub max_object_size: MaybeString, + #[yaserde(rename = "processVary")] + pub process_vary: i32, + #[yaserde(rename = "maxSecondaryEntries")] + pub max_secondary_entries: i32, } #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] pub struct HAProxyFrontends { - #[yaserde(rename = "frontend")] pub frontend: Vec, } @@ -1436,64 +1493,108 @@ pub struct Frontend { pub name: String, pub description: String, pub bind: String, - pub bindOptions: MaybeString, + #[yaserde(rename = "bindOptions")] + pub bind_options: MaybeString, pub mode: String, - pub defaultBackend: String, + #[yaserde(rename = "defaultBackend")] + pub default_backend: String, pub ssl_enabled: i32, pub ssl_certificates: MaybeString, pub ssl_default_certificate: MaybeString, - pub ssl_customOptions: MaybeString, - pub ssl_advancedEnabled: i32, - pub ssl_bindOptions: String, - pub ssl_minVersion: String, - pub ssl_maxVersion: MaybeString, - pub ssl_cipherList: String, - pub ssl_cipherSuites: String, - pub ssl_hstsEnabled: i32, - pub ssl_hstsIncludeSubDomains: i32, - pub ssl_hstsPreload: i32, - pub ssl_hstsMaxAge: i32, - pub ssl_clientAuthEnabled: i32, - pub ssl_clientAuthVerify: String, - pub ssl_clientAuthCAs: MaybeString, - pub ssl_clientAuthCRLs: MaybeString, - pub basicAuthEnabled: i32, - pub basicAuthUsers: MaybeString, - pub basicAuthGroups: MaybeString, - pub tuning_maxConnections: MaybeString, - pub tuning_timeoutClient: MaybeString, - pub tuning_timeoutHttpReq: MaybeString, - pub tuning_timeoutHttpKeepAlive: MaybeString, - pub linkedCpuAffinityRules: MaybeString, + #[yaserde(rename = "ssl_customOptions")] + pub ssl_custom_options: MaybeString, + #[yaserde(rename = "ssl_advancedEnabled")] + pub ssl_advanced_enabled: i32, + #[yaserde(rename = "ssl_bindOptions")] + pub ssl_bind_options: String, + #[yaserde(rename = "ssl_minVersion")] + pub ssl_min_version: String, + #[yaserde(rename = "ssl_maxVersion")] + pub ssl_max_version: MaybeString, + #[yaserde(rename = "ssl_cipherList")] + pub ssl_cipher_list: String, + #[yaserde(rename = "ssl_cipherSuites")] + pub ssl_cipher_suites: String, + #[yaserde(rename = "ssl_hstsEnabled")] + pub ssl_hsts_enabled: i32, + #[yaserde(rename = "ssl_hstsIncludeSubDomains")] + pub ssl_hsts_include_sub_domains: i32, + #[yaserde(rename = "ssl_hstsPreload")] + pub ssl_hsts_preload: i32, + #[yaserde(rename = "ssl_hstsMaxAge")] + pub ssl_hsts_max_age: i32, + #[yaserde(rename = "ssl_clientAuthEnabled")] + pub ssl_client_auth_enabled: i32, + #[yaserde(rename = "ssl_clientAuthVerify")] + pub ssl_client_auth_verify: String, + #[yaserde(rename = "ssl_clientAuthCAs")] + pub ssl_client_auth_cas: MaybeString, + #[yaserde(rename = "ssl_clientAuthCRLs")] + pub ssl_client_auth_cr_ls: MaybeString, + #[yaserde(rename = "basicAuthEnabled")] + pub basic_auth_enabled: i32, + #[yaserde(rename = "basicAuthUsers")] + pub basic_auth_users: MaybeString, + #[yaserde(rename = "basicAuthGroups")] + pub basic_auth_groups: MaybeString, + #[yaserde(rename = "tuning_maxConnections")] + pub tuning_max_connections: MaybeString, + #[yaserde(rename = "tuning_timeoutClient")] + pub tuning_timeout_client: MaybeString, + #[yaserde(rename = "tuning_timeoutHttpReq")] + pub tuning_timeout_http_req: MaybeString, + #[yaserde(rename = "tuning_timeoutHttpKeepAlive")] + pub tuning_timeout_http_keep_alive: MaybeString, + #[yaserde(rename = "linkedCpuAffinityRules")] + pub linked_cpu_affinity_rules: MaybeString, pub tuning_shards: MaybeString, - pub logging_dontLogNull: i32, - pub logging_dontLogNormal: i32, - pub logging_logSeparateErrors: i32, - pub logging_detailedLog: i32, - pub logging_socketStats: i32, + #[yaserde(rename = "logging_dontLogNull")] + pub logging_dont_log_null: i32, + #[yaserde(rename = "logging_dontLogNormal")] + pub logging_dont_log_normal: i32, + #[yaserde(rename = "logging_logSeparateErrors")] + pub logging_log_separate_errors: i32, + #[yaserde(rename = "logging_detailedLog")] + pub logging_detailed_log: i32, + #[yaserde(rename = "logging_socketStats")] + pub logging_socket_stats: i32, pub stickiness_pattern: MaybeString, - pub stickiness_dataTypes: MaybeString, + #[yaserde(rename = "stickiness_dataTypes")] + pub stickiness_data_types: MaybeString, pub stickiness_expire: String, pub stickiness_size: String, pub stickiness_counter: i32, pub stickiness_counter_key: String, pub stickiness_length: MaybeString, - pub stickiness_connRatePeriod: String, - pub stickiness_sessRatePeriod: String, - pub stickiness_httpReqRatePeriod: String, - pub stickiness_httpErrRatePeriod: String, - pub stickiness_bytesInRatePeriod: String, - pub stickiness_bytesOutRatePeriod: String, - pub http2Enabled: i32, - pub http2Enabled_nontls: i32, + #[yaserde(rename = "stickiness_connRatePeriod")] + pub stickiness_conn_rate_period: String, + #[yaserde(rename = "stickiness_sessRatePeriod")] + pub stickiness_sess_rate_period: String, + #[yaserde(rename = "stickiness_httpReqRatePeriod")] + pub stickiness_http_req_rate_period: String, + #[yaserde(rename = "stickiness_httpErrRatePeriod")] + pub stickiness_http_err_rate_period: String, + #[yaserde(rename = "stickiness_bytesInRatePeriod")] + pub stickiness_bytes_in_rate_period: String, + #[yaserde(rename = "stickiness_bytesOutRatePeriod")] + pub stickiness_bytes_out_rate_period: String, + #[yaserde(rename = "http2Enabled")] + pub http2_enabled: i32, + #[yaserde(rename = "http2Enabled_nontls")] + pub http2_enabled_nontls: i32, pub advertised_protocols: String, - pub forwardFor: i32, + #[yaserde(rename = "forwardFor")] + pub forward_for: i32, pub prometheus_enabled: i32, pub prometheus_path: String, - pub connectionBehaviour: String, - pub customOptions: MaybeString, - pub linkedActions: MaybeString, - pub linkedErrorfiles: MaybeString, + #[yaserde(rename = "connectionBehaviour")] + pub connection_behaviour: String, + #[yaserde(rename = "customOptions")] + pub custom_options: MaybeString, + #[yaserde(rename = "linkedActions")] + pub linked_actions: MaybeString, + #[yaserde(rename = "linkedErrorfiles")] + pub linked_error_files: MaybeString, } #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] @@ -1628,29 +1729,19 @@ pub struct HAProxyServers { pub struct HAProxyServer { #[yaserde(attribute, rename = "uuid")] pub uuid: String, - #[yaserde(rename = "id")] pub id: String, - #[yaserde(rename = "enabled")] pub enabled: u8, - #[yaserde(rename = "name")] pub name: String, - #[yaserde(rename = "description")] pub description: MaybeString, - #[yaserde(rename = "address")] pub address: String, - #[yaserde(rename = "port")] pub port: u16, - #[yaserde(rename = "checkport")] pub checkport: MaybeString, - #[yaserde(rename = "mode")] pub mode: String, - #[yaserde(rename = "multiplexer_protocol")] pub multiplexer_protocol: String, #[yaserde(rename = "type")] pub server_type: String, #[yaserde(rename = "serviceName")] pub service_name: MaybeString, - #[yaserde(rename = "number")] pub number: MaybeString, #[yaserde(rename = "linkedResolver")] pub linked_resolver: MaybeString, @@ -1658,7 +1749,6 @@ pub struct HAProxyServer { pub resolver_opts: MaybeString, #[yaserde(rename = "resolvePrefer")] pub resolve_prefer: MaybeString, - #[yaserde(rename = "ssl")] pub ssl: u8, #[yaserde(rename = "sslSNI")] pub ssl_sni: MaybeString, @@ -1672,15 +1762,12 @@ pub struct HAProxyServer { pub ssl_client_certificate: MaybeString, #[yaserde(rename = "maxConnections")] pub max_connections: MaybeString, - #[yaserde(rename = "weight")] pub weight: u32, #[yaserde(rename = "checkInterval")] pub check_interval: MaybeString, #[yaserde(rename = "checkDownInterval")] pub check_down_interval: MaybeString, - #[yaserde(rename = "source")] pub source: MaybeString, - #[yaserde(rename = "advanced")] pub advanced: MaybeString, #[yaserde(rename = "unix_socket")] pub unix_socket: MaybeString, diff --git a/harmony-rs/opnsense-config-xml/src/lib.rs b/harmony-rs/opnsense-config-xml/src/lib.rs new file mode 100644 index 0000000..aa934ca --- /dev/null +++ b/harmony-rs/opnsense-config-xml/src/lib.rs @@ -0,0 +1,3 @@ +mod xml_utils; +mod data; +pub use data::*; diff --git a/harmony-rs/opnsense-config/src/infra/generic_xml.rs b/harmony-rs/opnsense-config-xml/src/xml_utils/generic_xml.rs similarity index 99% rename from harmony-rs/opnsense-config/src/infra/generic_xml.rs rename to harmony-rs/opnsense-config-xml/src/xml_utils/generic_xml.rs index dfeae73..a1d4fff 100644 --- a/harmony-rs/opnsense-config/src/infra/generic_xml.rs +++ b/harmony-rs/opnsense-config-xml/src/xml_utils/generic_xml.rs @@ -118,7 +118,7 @@ impl YaSerializeTrait for RawXml { #[cfg(test)] mod test { - use crate::infra::yaserde::to_xml_str; + use crate::xml_utils::to_xml_str; use super::*; use pretty_assertions::assert_eq; diff --git a/harmony-rs/opnsense-config/src/infra/maybe_string.rs b/harmony-rs/opnsense-config-xml/src/xml_utils/maybe_string.rs similarity index 100% rename from harmony-rs/opnsense-config/src/infra/maybe_string.rs rename to harmony-rs/opnsense-config-xml/src/xml_utils/maybe_string.rs diff --git a/harmony-rs/opnsense-config-xml/src/xml_utils/mod.rs b/harmony-rs/opnsense-config-xml/src/xml_utils/mod.rs new file mode 100644 index 0000000..2866d26 --- /dev/null +++ b/harmony-rs/opnsense-config-xml/src/xml_utils/mod.rs @@ -0,0 +1,6 @@ +mod generic_xml; +mod maybe_string; +mod yaserde; +pub use generic_xml::*; +pub use maybe_string::*; +pub use yaserde::*; diff --git a/harmony-rs/opnsense-config/src/infra/yaserde.rs b/harmony-rs/opnsense-config-xml/src/xml_utils/yaserde.rs similarity index 100% rename from harmony-rs/opnsense-config/src/infra/yaserde.rs rename to harmony-rs/opnsense-config-xml/src/xml_utils/yaserde.rs diff --git a/harmony-rs/opnsense-config/Cargo.toml b/harmony-rs/opnsense-config/Cargo.toml index ca7a3ea..8acb92d 100644 --- a/harmony-rs/opnsense-config/Cargo.toml +++ b/harmony-rs/opnsense-config/Cargo.toml @@ -1,7 +1,9 @@ [package] name = "opnsense-config" -version = "0.1.0" edition = "2021" +version.workspace = true +readme.workspace = true +license.workspace = true [dependencies] serde = { version = "1.0.123", features = [ "derive" ] } @@ -9,14 +11,10 @@ log = { workspace = true } env_logger = { workspace = true } russh = { workspace = true } russh-keys = { workspace = true } -#yaserde = { git = "https://git.nationtech.io/NationTech/yaserde" } -#yaserde_derive = { git = "https://git.nationtech.io/NationTech/yaserde" } -yaserde = { path = "../../../../github/yaserde/yaserde" } -yaserde_derive = { path = "../../../../github/yaserde/yaserde_derive" } -xml-rs = "0.8" thiserror = "1.0" async-trait = { workspace = true } tokio = { workspace = true } +opnsense-config-xml = { path = "../opnsense-config-xml" } [dev-dependencies] pretty_assertions = "1.4.1" diff --git a/harmony-rs/opnsense-config/src/config.rs b/harmony-rs/opnsense-config/src/config.rs index 947e774..36681f6 100644 --- a/harmony-rs/opnsense-config/src/config.rs +++ b/harmony-rs/opnsense-config/src/config.rs @@ -1,10 +1,10 @@ use crate::error::Error; -use crate::modules::opnsense::OPNsense; use async_trait::async_trait; -use log::info; +use log::{info, trace}; +use opnsense_config_xml::OPNsense; use russh::client::{Config as SshConfig, Handler}; use russh_keys::key; -use std::{fs, net::Ipv4Addr, path::Path, sync::Arc}; +use std::{fs, net::Ipv4Addr, sync::Arc}; #[async_trait] pub trait ConfigRepository: std::fmt::Debug { @@ -87,6 +87,7 @@ impl ConfigRepository for SshConfigRepository { .await?; let mut channel = ssh.channel_open_session().await?; + todo!("Backup, Validate, Reload config file"); let command = format!( "echo '{}' > /conf/config.xml", @@ -144,9 +145,9 @@ pub struct Config { impl Config { pub async fn new(repository: Box) -> Result { let xml = repository.load().await?; - info!("xml {}", xml); + trace!("xml {}", xml); - let opnsense = yaserde::de::from_str(&xml).map_err(|e| Error::Xml(e.to_string()))?; + let opnsense = OPNsense::from(xml); Ok(Self { opnsense, @@ -163,15 +164,12 @@ impl Config { } pub async fn save(&self) -> Result<(), Error> { - let xml = yaserde::ser::to_string(&self.opnsense).map_err(|e| Error::Xml(e.to_string()))?; - self.repository.save(&xml).await + self.repository.save(&self.opnsense.to_xml()).await } } #[cfg(test)] mod tests { - use crate::infra::yaserde::to_xml_str; - use super::*; use std::path::PathBuf; use pretty_assertions::assert_eq; @@ -191,11 +189,33 @@ mod tests { println!("Config {:?}", config); - let serialized = to_xml_str(&config.opnsense).unwrap(); + let serialized = config.opnsense.to_xml(); fs::write("/tmp/serialized.xml", &serialized).unwrap(); assert_eq!(config_file_str, serialized); } + #[tokio::test] + async fn test_add_dhcpd_static_entry() { + let mut test_file_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + test_file_path.push("src/tests/data/config-full-1.xml"); + + let config_file_path = test_file_path.to_str().unwrap().to_string(); + println!("File path {config_file_path}"); + let repository = Box::new(LocalFileConfigRepository::new(config_file_path)); + let config_file_str = repository.load().await.unwrap(); + let mut config = Config::new(repository) + .await + .expect("Failed to load config"); + + println!("Config {:?}", config); + + let serialized = config.opnsense.to_xml(); + + fs::write("/tmp/serialized.xml", &serialized).unwrap(); + + assert_eq!(config_file_str, serialized); + todo!(); + } } diff --git a/harmony-rs/opnsense-config/src/infra/mod.rs b/harmony-rs/opnsense-config/src/infra/mod.rs deleted file mode 100644 index 9c67f9a..0000000 --- a/harmony-rs/opnsense-config/src/infra/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod generic_xml; -pub mod maybe_string; -pub mod yaserde; - diff --git a/harmony-rs/opnsense-config/src/lib.rs b/harmony-rs/opnsense-config/src/lib.rs index e0e5967..6625b3a 100644 --- a/harmony-rs/opnsense-config/src/lib.rs +++ b/harmony-rs/opnsense-config/src/lib.rs @@ -1,7 +1,23 @@ pub mod config; -pub mod modules; pub mod error; -pub mod infra; +pub mod modules; +use std::net::Ipv4Addr; pub use config::Config; pub use error::Error; +use modules::dhcp::DhcpConfig; +use opnsense_config_xml::OPNsense; + +#[tokio::test] +async fn test_public_sdk() { + let mut opnsense = OPNsense::default(); + let mut dhcpd = DhcpConfig::new(&mut opnsense); + dhcpd.add_static_mapping( + "test_mac", + Ipv4Addr::new(192, 168, 168, 168), + "test_hostname", + ); + + todo!(); + // opnsense.apply_changes().await; +} diff --git a/harmony-rs/opnsense-config/src/modules/deserializer/interfaces.rs b/harmony-rs/opnsense-config/src/modules/deserializer/interfaces.rs deleted file mode 100644 index 277048f..0000000 --- a/harmony-rs/opnsense-config/src/modules/deserializer/interfaces.rs +++ /dev/null @@ -1,111 +0,0 @@ -use xml::reader::XmlEvent; -use yaserde::{YaDeserialize, YaSerialize}; - -use crate::modules::opnsense::{Interface, Interfaces}; - -impl YaSerialize for Interfaces { - fn serialize( - &self, - writer: &mut yaserde::ser::Serializer, - ) -> Result<(), String> { - writer.write("Interfaces serializer TODO"); - Ok(()) - } - - fn serialize_attributes( - &self, - attributes: Vec, - namespace: xml::namespace::Namespace, - ) -> Result< - ( - Vec, - xml::namespace::Namespace, - ), - String, - > { - todo!() - } -} - -impl YaDeserialize for Interfaces { - fn deserialize( - reader: &mut yaserde::de::Deserializer, - ) -> Result { - let mut interfaces = Interfaces::default(); - let mut current_interface_name = String::new(); - let mut current_interface = Interface::default(); - - let mut event = reader.next_event()?; - loop { - match event { - xml::reader::XmlEvent::EndElement { name } => { - println!( - "Handling EndElement {}, current_interface_name: {}", - name.local_name, current_interface_name - ); - println!("Peeking after EndElement {:?}", reader.peek()?); - if name.local_name == "interfaces" { - break; - } - todo!("Should not hit here"); - } - xml::reader::XmlEvent::StartElement { ref name, .. } => { - println!("Got start Element {:?}", name); - current_interface_name = name.local_name.clone(); - println!("About to deserialize interface from name {:?}", name); - // TODO store names - 'inner_iface: loop { - let peek = reader.peek()?; - println!("Peeking before forcing next event {:?}", peek); - - if let XmlEvent::EndElement { name } = peek { - // TODO save the interface name and struct in the hashmap - println!("Forcing next_event"); - reader.next_event()?; - - let peek = reader.peek()?; - println!("Peeking after next event deserializing {:?}", peek); - if let XmlEvent::EndElement { name } = peek { - println!("Got two EndElement in a row, breaking out of loop"); - break 'inner_iface; - } - } - println!("Peeking before deserializing {:?}", reader.peek()?); - let interface = ::deserialize(reader)?; - println!("Interface deserialized {:?}", interface); - println!("Peeking after deserializing {:?}", reader.peek()?); - } - println!( - "Done with inner interface, loop completed {:?}", - reader.peek()? - ); - } - xml::reader::XmlEvent::EndDocument => break, - _ => { - return Err( - "This Interfaces Deserializer does not support all XmlEvent types".into(), - ) - } - } - let peek = reader.peek()?; - - let read_next = true; - if let XmlEvent::EndElement { name } = peek { - if name.local_name == "interfaces" { - println!("Got endElement interfaces, not reading next event"); - break; - } - } - - if read_next { - event = reader.next_event()?; - } - println!("Outer loop got event {:?}", event); - println!("Outer loop got peek {:?}", reader.peek()?); - } - println!("Done with interfaces {:?}", interfaces); - println!("reader peeking shows {:?}", reader.peek()); - - Ok(interfaces) - } -} diff --git a/harmony-rs/opnsense-config/src/modules/deserializer/mod.rs b/harmony-rs/opnsense-config/src/modules/deserializer/mod.rs deleted file mode 100644 index e358e80..0000000 --- a/harmony-rs/opnsense-config/src/modules/deserializer/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ - -mod interfaces; - diff --git a/harmony-rs/opnsense-config/src/modules/dhcp.rs b/harmony-rs/opnsense-config/src/modules/dhcp.rs index 0b5b766..4b50d18 100644 --- a/harmony-rs/opnsense-config/src/modules/dhcp.rs +++ b/harmony-rs/opnsense-config/src/modules/dhcp.rs @@ -1,12 +1,9 @@ +use opnsense_config_xml::Range; +use opnsense_config_xml::StaticMap; use std::cmp::Ordering; -use std::net::IpAddr; use std::net::Ipv4Addr; -use super::opnsense::{OPNsense, StaticMap}; -use crate::infra::maybe_string::MaybeString; -use crate::modules::opnsense::NumberOption; -use crate::modules::opnsense::Range; -use yaserde_derive::{YaDeserialize, YaSerialize}; +use opnsense_config_xml::OPNsense; pub struct DhcpConfig<'a> { opnsense: &'a mut OPNsense, @@ -48,41 +45,48 @@ impl<'a> DhcpConfig<'a> { pub fn add_static_mapping( &mut self, - mac: String, + mac: &str, ipaddr: Ipv4Addr, - hostname: String, + hostname: &str, ) -> Result<(), DhcpError> { - let range: Range = todo!(); + let mac = mac.to_string(); + let hostname = hostname.to_string(); + let range = &self.opnsense.dhcpd.lan.range; + if !Self::is_valid_mac(&mac) { return Err(DhcpError::InvalidMacAddress(mac)); } - // if !Self::is_ip_in_range(&ipaddr, range) { - // return Err(DhcpError::IpAddressOutOfRange(ipaddr)); - // } + if !Self::is_ip_in_range(&ipaddr, range) { + return Err(DhcpError::IpAddressOutOfRange(ipaddr.to_string())); + } let existing_mappings = &self.opnsense.dhcpd.lan.staticmaps; - // TODO - // if existing_mappings.iter().any(|m| m.ipaddr == ipaddr) { - // return Err(DhcpError::IpAddressAlreadyMapped(ipaddr)); - // } + if existing_mappings.iter().any(|m| { + m.ipaddr + .parse::() + .expect("Mapping contains invalid ipv4") + == ipaddr + }) { + return Err(DhcpError::IpAddressAlreadyMapped(ipaddr.to_string())); + } - // if existing_mappings.iter().any(|m| m.mac == mac) { - // return Err(DhcpError::MacAddressAlreadyMapped(mac)); - // } + if existing_mappings.iter().any(|m| m.mac == mac) { + return Err(DhcpError::MacAddressAlreadyMapped(mac)); + } - // let static_map = StaticMap { - // mac, - // ipaddr, - // hostname, - // descr: Default::default(), - // winsserver: Default::default(), - // dnsserver: Default::default(), - // ntpserver: Default::default(), - // }; + let static_map = StaticMap { + mac, + ipaddr: ipaddr.to_string(), + hostname, + descr: Default::default(), + winsserver: Default::default(), + dnsserver: Default::default(), + ntpserver: Default::default(), + }; - // self.opnsense.dhcpd.lan.staticmaps.push(static_map); + self.opnsense.dhcpd.lan.staticmaps.push(static_map); Ok(()) } @@ -101,7 +105,7 @@ impl<'a> DhcpConfig<'a> { .all(|part| part.len() == 2 && part.chars().all(|c| c.is_ascii_hexdigit())) } - fn is_ip_in_range(ip: &Ipv4Addr, range: Range) -> bool { + fn is_ip_in_range(ip: &Ipv4Addr, range: &Range) -> bool { let range_start = range .from .parse::() @@ -121,114 +125,11 @@ impl<'a> DhcpConfig<'a> { } } -#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] -#[yaserde(rename = "dhcpd")] -pub struct Dhcpd { - #[yaserde(rename = "lan")] - pub lan: DhcpInterface, -} - -#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] -pub struct DhcpInterface { - pub enable: i32, - pub gateway: String, - pub domain: String, - #[yaserde(rename = "ddnsdomainalgorithm")] - pub ddns_domain_algorithm: String, - #[yaserde(rename = "numberoptions")] - pub number_options: Vec, - #[yaserde(rename = "range")] - pub range: Range, - pub winsserver: MaybeString, - pub dnsserver: MaybeString, - pub ntpserver: MaybeString, - #[yaserde(rename = "staticmap")] - pub staticmaps: Vec, - pub pool: MaybeString, -} - -#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] -pub struct DhcpRange { - #[yaserde(rename = "from")] - pub from: String, - #[yaserde(rename = "to")] - pub to: String, -} - #[cfg(test)] mod test { - use std::net::Ipv4Addr; - use crate::infra::yaserde::to_xml_str; - use super::*; use pretty_assertions::assert_eq; - - #[test] - fn dhcpd_should_deserialize_serialize_identical() { - let dhcpd: Dhcpd = - yaserde::de::from_str(SERIALIZED_DHCPD).expect("Deserialize Dhcpd failed"); - - assert_eq!( - to_xml_str(&dhcpd).expect("Serialize Dhcpd failed"), - SERIALIZED_DHCPD - ); - } - - const SERIALIZED_DHCPD: &str = " - - - 1 - 192.168.20.1 - somedomain.yourlocal.mcd - hmac-md5 - - - - - 192.168.20.50 - 192.168.20.200 - - - 192.168.20.1 - - - 55:55:55:55:55:1c - 192.168.20.160 - somehost983 - someservire8 - - - - - - 55:55:55:55:55:1c - 192.168.20.155 - somehost893 - - - - - - 55:55:55:55:55:1c - 192.168.20.165 - somehost893 - - - - - - - 55:55:55:55:55:1c - 192.168.20.50 - hostswitch2 - switch-2 (bottom) - - - - - - -\n"; + use std::net::Ipv4Addr; #[test] fn test_ip_in_range() { @@ -239,22 +140,23 @@ mod test { // Test IP within range let ip = "192.168.1.150".parse::().unwrap(); - assert_eq!(DhcpConfig::is_ip_in_range(&ip, range.clone()), true); + assert_eq!(DhcpConfig::is_ip_in_range(&ip, &range), true); // Test IP at start of range let ip = "192.168.1.100".parse::().unwrap(); - assert_eq!(DhcpConfig::is_ip_in_range(&ip, range.clone()), true); + assert_eq!(DhcpConfig::is_ip_in_range(&ip, &range), true); // Test IP at end of range let ip = "192.168.1.200".parse::().unwrap(); - assert_eq!(DhcpConfig::is_ip_in_range(&ip, range.clone()), true); + assert_eq!(DhcpConfig::is_ip_in_range(&ip, &range), true); // Test IP before range let ip = "192.168.1.99".parse::().unwrap(); - assert_eq!(DhcpConfig::is_ip_in_range(&ip, range.clone()), false); + assert_eq!(DhcpConfig::is_ip_in_range(&ip, &range), false); // Test IP after range let ip = "192.168.1.201".parse::().unwrap(); - assert_eq!(DhcpConfig::is_ip_in_range(&ip, range.clone()), false); + assert_eq!(DhcpConfig::is_ip_in_range(&ip, &range), false); } + } diff --git a/harmony-rs/opnsense-config/src/modules/mod.rs b/harmony-rs/opnsense-config/src/modules/mod.rs index 0518c7f..5833493 100644 --- a/harmony-rs/opnsense-config/src/modules/mod.rs +++ b/harmony-rs/opnsense-config/src/modules/mod.rs @@ -1,3 +1 @@ -pub mod opnsense; pub mod dhcp; -mod deserializer;