wip(opnsense-config): Refactoring to improve usability and features

This commit is contained in:
Jean-Gabriel Gill-Couture 2024-11-14 23:45:13 -05:00
parent 85786cf648
commit 65c395aeae
20 changed files with 754 additions and 455 deletions

14
harmony-rs/Cargo.lock generated
View File

@ -1248,12 +1248,26 @@ dependencies = [
"async-trait", "async-trait",
"env_logger", "env_logger",
"log", "log",
"opnsense-config-xml",
"pretty_assertions", "pretty_assertions",
"russh", "russh",
"russh-keys", "russh-keys",
"serde", "serde",
"thiserror", "thiserror",
"tokio", "tokio",
]
[[package]]
name = "opnsense-config-xml"
version = "0.1.0"
dependencies = [
"async-trait",
"env_logger",
"log",
"pretty_assertions",
"serde",
"thiserror",
"tokio",
"xml-rs", "xml-rs",
"yaserde", "yaserde",
"yaserde_derive", "yaserde_derive",

View File

@ -3,7 +3,7 @@ resolver = "2"
members = [ members = [
"private_repos/*", "private_repos/*",
"harmony", "harmony",
"opnsense-config", "opnsense-config", "opnsense-config-xml",
] ]
[workspace.package] [workspace.package]

View File

@ -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"

View File

@ -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<NumberOption>,
#[yaserde(rename = "range")]
pub range: Range,
pub winsserver: MaybeString,
pub dnsserver: MaybeString,
pub ntpserver: MaybeString,
#[yaserde(rename = "staticmap")]
pub staticmaps: Vec<StaticMap>,
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 = "<?xml version=\"1.0\"?>
<dhcpd>
<lan>
<enable>1</enable>
<gateway>192.168.20.1</gateway>
<domain>somedomain.yourlocal.mcd</domain>
<ddnsdomainalgorithm>hmac-md5</ddnsdomainalgorithm>
<numberoptions>
<item/>
</numberoptions>
<range>
<from>192.168.20.50</from>
<to>192.168.20.200</to>
</range>
<winsserver/>
<dnsserver>192.168.20.1</dnsserver>
<ntpserver/>
<staticmap>
<mac>55:55:55:55:55:1c</mac>
<ipaddr>192.168.20.160</ipaddr>
<hostname>somehost983</hostname>
<descr>someservire8</descr>
<winsserver/>
<dnsserver/>
<ntpserver/>
</staticmap>
<staticmap>
<mac>55:55:55:55:55:1c</mac>
<ipaddr>192.168.20.155</ipaddr>
<hostname>somehost893</hostname>
<winsserver/>
<dnsserver/>
<ntpserver/>
</staticmap>
<staticmap>
<mac>55:55:55:55:55:1c</mac>
<ipaddr>192.168.20.165</ipaddr>
<hostname>somehost893</hostname>
<descr/>
<winsserver/>
<dnsserver/>
<ntpserver/>
</staticmap>
<staticmap>
<mac>55:55:55:55:55:1c</mac>
<ipaddr>192.168.20.50</ipaddr>
<hostname>hostswitch2</hostname>
<descr>switch-2 (bottom)</descr>
<winsserver/>
<dnsserver/>
<ntpserver/>
</staticmap>
<pool/>
</lan>
</dhcpd>\n";
}

View File

@ -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<String, Interface>,
}
#[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<MaybeString>,
pub internal_dynamic: Option<MaybeString>,
pub ipaddr: Option<MaybeString>,
#[yaserde(rename = "blockpriv")]
pub block_priv: Option<MaybeString>,
#[yaserde(rename = "blockbogons")]
pub block_bogons: Option<MaybeString>,
pub lock: Option<MaybeString>,
#[yaserde(rename = "type")]
pub r#type: Option<MaybeString>,
#[yaserde(rename = "virtual")]
pub r#virtual: Option<MaybeString>,
pub subnet: Option<MaybeString>,
pub networks: Option<MaybeString>,
pub subnetv6: Option<MaybeString>,
pub ipaddrv6: Option<MaybeString>,
#[yaserde(rename = "track6-interface")]
pub track6_interface: Option<MaybeString>,
#[yaserde(rename = "track6-prefix-id")]
pub track6_prefix_id: Option<MaybeString>,
}
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::<Interfaces>(FULL_INTERFACES_XML).unwrap();
assert_eq!(interfaces.interfaces.len(), 6)
}
const FULL_INTERFACES_XML: &str = "<interfaces>
<wan>
<if>pppoe0</if>
<descr>WAN</descr>
<enable>1</enable>
<lock>1</lock>
<spoofmac/>
<blockpriv>1</blockpriv>
<blockbogons>1</blockbogons>
<ipaddr>pppoe</ipaddr>
</wan>
<lan>
<if>em1</if>
<descr>LAN</descr>
<enable>1</enable>
<spoofmac/>
<ipaddr>192.168.20.1</ipaddr>
<subnet>24</subnet>
<ipaddrv6>track6</ipaddrv6>
<track6-interface/>
<track6-prefix-id>0</track6-prefix-id>
</lan>
<lo0>
<internal_dynamic>1</internal_dynamic>
<descr>Loopback</descr>
<enable>1</enable>
<if>lo0</if>
<ipaddr>127.0.0.1</ipaddr>
<ipaddrv6>::1</ipaddrv6>
<subnet>8</subnet>
<subnetv6>128</subnetv6>
<type>none</type>
<virtual>1</virtual>
</lo0>
<opt1>
<if>em5</if>
<descr>backup_sync</descr>
<enable>1</enable>
<lock>1</lock>
<spoofmac/>
<ipaddr>10.10.5.1</ipaddr>
<subnet>24</subnet>
</opt1>
<wireguard>
<internal_dynamic>1</internal_dynamic>
<descr>WireGuard (Group)</descr>
<if>wireguard</if>
<virtual>1</virtual>
<enable>1</enable>
<type>group</type>
<networks/>
</wireguard>
<openvpn>
<internal_dynamic>1</internal_dynamic>
<enable>1</enable>
<if>openvpn</if>
<descr>OpenVPN</descr>
<type>group</type>
<virtual>1</virtual>
<networks/>
</openvpn>
</interfaces>";
}
// impl YaSerializeTrait for Interfaces {
// fn serialize<W: std::io::Write>(
// &self,
// writer: &mut yaserde::ser::Serializer<W>,
// ) -> Result<(), String> {
// writer.write("Interfaces serializer TODO").map_err(|e| e.to_string())
// }
//
// fn serialize_attributes(
// &self,
// attributes: Vec<xml::attribute::OwnedAttribute>,
// namespace: xml::namespace::Namespace,
// ) -> Result<
// (
// Vec<xml::attribute::OwnedAttribute>,
// xml::namespace::Namespace,
// ),
// String,
// > {
// todo!()
// }
// }
//
// impl YaDeserializeTrait for Interfaces {
// fn deserialize<R: std::io::Read>(
// reader: &mut yaserde::de::Deserializer<R>,
// ) -> Result<Self, String> {
// 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 = <Interface as yaserde::YaDeserialize>::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)
// }
// }

View File

@ -0,0 +1,6 @@
mod opnsense;
mod interfaces;
mod dhcpd;
pub use opnsense::*;
pub use interfaces::*;
pub use dhcpd::*;

View File

@ -1,7 +1,24 @@
use super::dhcp::Dhcpd; use crate::data::dhcpd::Dhcpd;
use crate::infra::{generic_xml::RawXml, maybe_string::MaybeString}; use crate::xml_utils::{MaybeString, RawXml};
use log::error;
use yaserde_derive::{YaDeserialize, YaSerialize}; use yaserde_derive::{YaDeserialize, YaSerialize};
impl From<String> 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)] #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
#[yaserde(rename = "opnsense")] #[yaserde(rename = "opnsense")]
pub struct OPNsense { pub struct OPNsense {
@ -19,7 +36,7 @@ pub struct OPNsense {
pub widgets: Widgets, pub widgets: Widgets,
pub revision: Revision, pub revision: Revision,
#[yaserde(rename = "OPNsense")] #[yaserde(rename = "OPNsense")]
pub opnsense: OPNsenseConfig, pub opnsense: OPNsenseXmlSection,
pub staticroutes: StaticRoutes, pub staticroutes: StaticRoutes,
pub ca: MaybeString, pub ca: MaybeString,
pub gateways: Gateways, pub gateways: Gateways,
@ -279,42 +296,6 @@ pub struct WebGui {
pub compression: MaybeString, pub compression: MaybeString,
} }
use std::collections::HashMap;
#[derive(Default, PartialEq, Debug)]
pub struct Interfaces {
pub interfaces: HashMap<String, Interface>,
}
#[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<u8>,
pub ipaddr: MaybeString,
#[yaserde(rename = "blockpriv")]
pub block_priv: Option<u8>,
#[yaserde(rename = "blockbogons")]
pub block_bogons: Option<u8>,
pub lock: Option<u8>,
#[yaserde(rename = "type")]
pub r#type: MaybeString,
#[yaserde(rename = "virtual")]
pub r#virtual: MaybeString,
pub subnet: MaybeString,
pub networks: Option<u8>,
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)] #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct NumberOption { pub struct NumberOption {
item: MaybeString, item: MaybeString,
@ -406,7 +387,7 @@ pub struct Created {
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
#[yaserde(rename = "OPNsense")] #[yaserde(rename = "OPNsense")]
pub struct OPNsenseConfig { pub struct OPNsenseXmlSection {
pub captiveportal: Option<CaptivePortal>, pub captiveportal: Option<CaptivePortal>,
pub cron: Option<Cron>, pub cron: Option<Cron>,
#[yaserde(rename = "Netflow")] #[yaserde(rename = "Netflow")]
@ -463,33 +444,41 @@ pub struct IDSGeneral {
promisc: Option<u8>, promisc: Option<u8>,
interfaces: String, interfaces: String,
homenet: String, homenet: String,
defaultPacketSize: MaybeString, #[yaserde(rename = "defaultPacketSize")]
UpdateCron: MaybeString, default_packet_size: MaybeString,
AlertLogrotate: String, #[yaserde(rename = "UpdateCron")]
AlertSaveLogs: u8, update_cron: MaybeString,
MPMAlgo: String, #[yaserde(rename = "AlertLogrotate")]
alert_logrotate: String,
#[yaserde(rename = "AlertSaveLogs")]
alert_save_logs: u8,
#[yaserde(rename = "MPMAlgo")]
mpm_algo: String,
detect: Detect, detect: Detect,
syslog: Option<u8>, syslog: Option<u8>,
syslog_eve: Option<u8>, syslog_eve: Option<u8>,
LogPayload: Option<u8>, #[yaserde(rename = "LogPayload")]
log_payload: Option<u8>,
verbosity: MaybeString, verbosity: MaybeString,
} }
#[derive(Debug, YaSerialize, YaDeserialize, PartialEq)] #[derive(Debug, YaSerialize, YaDeserialize, PartialEq)]
pub struct Detect { pub struct Detect {
Profile: String, #[yaserde(rename = "Profile")]
profile: String,
toclient_groups: MaybeString, toclient_groups: MaybeString,
toserver_groups: MaybeString, toserver_groups: MaybeString,
} }
#[derive(Debug, YaSerialize, YaDeserialize, PartialEq)] #[derive(Debug, YaSerialize, YaDeserialize, PartialEq)]
#[yaserde(rename = "IPsec")]
pub struct IPsec { pub struct IPsec {
#[yaserde(attribute)] #[yaserde(attribute)]
version: String, version: String,
general: GeneralIpsec, general: GeneralIpsec,
keyPairs: MaybeString, #[yaserde(rename = "keyPairs")]
preSharedKeys: MaybeString, key_pairs: MaybeString,
#[yaserde(rename = "preSharedKeys")]
pre_shared_keys: MaybeString,
} }
#[derive(Debug, YaSerialize, YaDeserialize, PartialEq)] #[derive(Debug, YaSerialize, YaDeserialize, PartialEq)]
@ -891,10 +880,14 @@ pub struct LocalCache {
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct Traffic { pub struct Traffic {
pub enabled: i32, pub enabled: i32,
pub maxDownloadSize: i32, #[yaserde(rename = "maxDownloadSize")]
pub maxUploadSize: i32, pub max_download_size: i32,
pub OverallBandwidthTrotteling: i32, #[yaserde(rename = "maxUploadSize")]
pub perHostTrotteling: i32, 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)] #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
@ -923,11 +916,16 @@ pub struct Forward {
pub snmp_enable: i32, pub snmp_enable: i32,
pub snmp_port: i32, pub snmp_port: i32,
pub snmp_password: String, pub snmp_password: String,
pub ftpInterfaces: MaybeString, #[yaserde(rename = "ftpInterfaces")]
pub ftpPort: i32, pub ftp_interfaces: MaybeString,
pub ftpTransparentMode: i32, #[yaserde(rename = "ftpPort")]
pub addACLforInterfaceSubnets: i32, pub ftp_port: i32,
pub transparentMode: 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 acl: Acl,
pub icap: Icap, pub icap: Icap,
pub authentication: Authentication, pub authentication: Authentication,
@ -935,47 +933,66 @@ pub struct Forward {
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct Acl { pub struct Acl {
pub allowedSubnets: MaybeString, #[yaserde(rename = "allowedSubnets")]
pub allowed_subnets: MaybeString,
pub unrestricted: MaybeString, pub unrestricted: MaybeString,
pub bannedHosts: MaybeString, #[yaserde(rename = "bannedHosts")]
pub whiteList: MaybeString, pub banned_hosts: MaybeString,
pub blackList: MaybeString, #[yaserde(rename = "whiteList")]
pub white_list: MaybeString,
#[yaserde(rename = "blackList")]
pub black_list: MaybeString,
pub browser: MaybeString, pub browser: MaybeString,
pub mimeType: MaybeString, #[yaserde(rename = "mimeType")]
pub googleapps: MaybeString, pub mime_type: MaybeString,
pub google_apps: MaybeString,
pub youtube: MaybeString, pub youtube: MaybeString,
pub safePorts: String, #[yaserde(rename = "safePorts")]
pub sslPorts: String, pub safe_ports: String,
pub remoteACLs: RemoteAcls, #[yaserde(rename = "sslPorts")]
pub ssl_ports: String,
#[yaserde(rename = "remoteACLs")]
pub remote_acls: RemoteAcls,
} }
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct RemoteAcls { pub struct RemoteAcls {
pub blacklists: MaybeString, pub blacklists: MaybeString,
pub UpdateCron: MaybeString, #[yaserde(rename = "UpdateCron")]
pub update_cron: MaybeString,
} }
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct Icap { pub struct Icap {
pub enable: i32, pub enable: i32,
pub RequestURL: String, #[yaserde(rename = "RequestURL")]
pub ResponseURL: String, pub request_url: String,
pub SendClientIP: i32, #[yaserde(rename = "ResponseURL")]
pub SendUsername: i32, pub response_url: String,
pub EncodeUsername: i32, #[yaserde(rename = "SendClientIP")]
pub UsernameHeader: String, pub send_client_ip: i32,
pub EnablePreview: i32, #[yaserde(rename = "SendUsername")]
pub PreviewSize: i32, pub send_username: i32,
pub OptionsTTL: 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, pub exclude: MaybeString,
} }
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct Authentication { pub struct Authentication {
pub method: MaybeString, pub method: MaybeString,
pub authEnforceGroup: MaybeString, #[yaserde(rename = "authEnforceGroup")]
pub auth_enforce_group: MaybeString,
pub realm: String, pub realm: String,
pub credentialsttl: i32, pub credentialsttl: i32, // This field is already in snake_case
pub children: i32, pub children: i32,
} }
@ -1263,8 +1280,11 @@ pub struct Account {
pub struct ConfigOpenVPN { pub struct ConfigOpenVPN {
#[yaserde(attribute)] #[yaserde(attribute)]
pub version: String, pub version: String,
#[yaserde(rename = "Overwrites")]
pub Overwrites: MaybeString, pub Overwrites: MaybeString,
#[yaserde(rename = "Instances")]
pub Instances: MaybeString, pub Instances: MaybeString,
#[yaserde(rename = "StaticKeys")]
pub StaticKeys: MaybeString, pub StaticKeys: MaybeString,
} }
@ -1327,12 +1347,18 @@ pub struct CronJobs {
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct HaProxyGeneral { pub struct HaProxyGeneral {
pub enabled: i32, pub enabled: i32,
pub gracefulStop: i32, #[yaserde(rename = "gracefulStop")]
pub hardStopAfter: String, pub graceful_stop: i32,
pub closeSpreadTime: MaybeString, #[yaserde(rename = "hardStopAfter")]
pub seamlessReload: i32, pub hard_stop_after: String,
pub storeOcsp: i32, #[yaserde(rename = "closeSpreadTime")]
pub showIntro: i32, 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 peers: Peers,
pub tuning: Tuning, pub tuning: Tuning,
pub defaults: HaProxyDefaults, pub defaults: HaProxyDefaults,
@ -1355,36 +1381,56 @@ pub struct Peers {
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct Tuning { pub struct Tuning {
pub root: i32, pub root: i32,
pub maxConnections: MaybeString, #[yaserde(rename = "maxConnections")]
pub max_connections: MaybeString,
pub nbthread: i32, pub nbthread: i32,
pub sslServerVerify: String, #[yaserde(rename = "sslServerVerify")]
pub maxDHSize: i32, pub ssl_server_verify: String,
pub bufferSize: i32, #[yaserde(rename = "maxDHSize")]
pub spreadChecks: i32, pub max_dh_size: i32,
pub bogusProxyEnabled: i32, #[yaserde(rename = "bufferSize")]
pub luaMaxMem: i32, pub buffer_size: i32,
pub customOptions: MaybeString, #[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")] #[yaserde(rename = "ssl_defaultsEnabled")]
pub ssl_defaults_enabled: i32, pub ssl_defaults_enabled: i32,
pub ssl_bindOptions: String, #[yaserde(rename = "ssl_bindOptions")]
pub ssl_minVersion: String, pub ssl_bind_options: String,
pub ssl_maxVersion: MaybeString, #[yaserde(rename = "ssl_minVersion")]
pub ssl_cipherList: String, pub ssl_min_version: String,
pub ssl_cipherSuites: 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)] #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct HaProxyDefaults { pub struct HaProxyDefaults {
pub maxConnections: MaybeString, #[yaserde(rename = "maxConnections")]
pub maxConnectionsServers: MaybeString, pub max_connections: MaybeString,
pub timeoutClient: String, #[yaserde(rename = "maxConnectionsServers")]
pub timeoutConnect: String, pub max_connections_servers: MaybeString,
pub timeoutCheck: MaybeString, #[yaserde(rename = "timeoutClient")]
pub timeoutServer: String, 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 retries: i32,
pub redispatch: String, pub redispatch: String,
pub init_addr: String, pub init_addr: String,
pub customOptions: MaybeString, #[yaserde(rename = "customOptions")]
pub custom_options: MaybeString,
} }
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
@ -1399,13 +1445,20 @@ pub struct HaProxyLogging {
pub struct Stats { pub struct Stats {
pub enabled: i32, pub enabled: i32,
pub port: i32, pub port: i32,
pub remoteEnabled: i32, #[yaserde(rename = "remoteEnabled")]
pub remoteBind: MaybeString, pub remote_enabled: i32,
pub authEnabled: i32, #[yaserde(rename = "remoteBind")]
pub remote_bind: MaybeString,
#[yaserde(rename = "authEnabled")]
pub auth_enabled: i32,
#[yaserde(rename = "users")]
pub users: MaybeString, pub users: MaybeString,
pub allowedUsers: MaybeString, #[yaserde(rename = "allowedUsers")]
pub allowedGroups: MaybeString, pub allowed_users: MaybeString,
pub customOptions: MaybeString, #[yaserde(rename = "allowedGroups")]
pub allowed_groups: MaybeString,
#[yaserde(rename = "customOptions")]
pub custom_options: MaybeString,
pub prometheus_enabled: i32, pub prometheus_enabled: i32,
pub prometheus_bind: String, pub prometheus_bind: String,
pub prometheus_path: String, pub prometheus_path: String,
@ -1414,16 +1467,20 @@ pub struct Stats {
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct HaProxyCache { pub struct HaProxyCache {
pub enabled: i32, pub enabled: i32,
pub totalMaxSize: i32, #[yaserde(rename = "totalMaxSize")]
pub maxAge: i32, pub total_max_size: i32,
pub maxObjectSize: MaybeString, #[yaserde(rename = "maxAge")]
pub processVary: i32, pub max_age: i32,
pub maxSecondaryEntries: 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)] #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct HAProxyFrontends { pub struct HAProxyFrontends {
#[yaserde(rename = "frontend")]
pub frontend: Vec<Frontend>, pub frontend: Vec<Frontend>,
} }
@ -1436,64 +1493,108 @@ pub struct Frontend {
pub name: String, pub name: String,
pub description: String, pub description: String,
pub bind: String, pub bind: String,
pub bindOptions: MaybeString, #[yaserde(rename = "bindOptions")]
pub bind_options: MaybeString,
pub mode: String, pub mode: String,
pub defaultBackend: String, #[yaserde(rename = "defaultBackend")]
pub default_backend: String,
pub ssl_enabled: i32, pub ssl_enabled: i32,
pub ssl_certificates: MaybeString, pub ssl_certificates: MaybeString,
pub ssl_default_certificate: MaybeString, pub ssl_default_certificate: MaybeString,
pub ssl_customOptions: MaybeString, #[yaserde(rename = "ssl_customOptions")]
pub ssl_advancedEnabled: i32, pub ssl_custom_options: MaybeString,
pub ssl_bindOptions: String, #[yaserde(rename = "ssl_advancedEnabled")]
pub ssl_minVersion: String, pub ssl_advanced_enabled: i32,
pub ssl_maxVersion: MaybeString, #[yaserde(rename = "ssl_bindOptions")]
pub ssl_cipherList: String, pub ssl_bind_options: String,
pub ssl_cipherSuites: String, #[yaserde(rename = "ssl_minVersion")]
pub ssl_hstsEnabled: i32, pub ssl_min_version: String,
pub ssl_hstsIncludeSubDomains: i32, #[yaserde(rename = "ssl_maxVersion")]
pub ssl_hstsPreload: i32, pub ssl_max_version: MaybeString,
pub ssl_hstsMaxAge: i32, #[yaserde(rename = "ssl_cipherList")]
pub ssl_clientAuthEnabled: i32, pub ssl_cipher_list: String,
pub ssl_clientAuthVerify: String, #[yaserde(rename = "ssl_cipherSuites")]
pub ssl_clientAuthCAs: MaybeString, pub ssl_cipher_suites: String,
pub ssl_clientAuthCRLs: MaybeString, #[yaserde(rename = "ssl_hstsEnabled")]
pub basicAuthEnabled: i32, pub ssl_hsts_enabled: i32,
pub basicAuthUsers: MaybeString, #[yaserde(rename = "ssl_hstsIncludeSubDomains")]
pub basicAuthGroups: MaybeString, pub ssl_hsts_include_sub_domains: i32,
pub tuning_maxConnections: MaybeString, #[yaserde(rename = "ssl_hstsPreload")]
pub tuning_timeoutClient: MaybeString, pub ssl_hsts_preload: i32,
pub tuning_timeoutHttpReq: MaybeString, #[yaserde(rename = "ssl_hstsMaxAge")]
pub tuning_timeoutHttpKeepAlive: MaybeString, pub ssl_hsts_max_age: i32,
pub linkedCpuAffinityRules: MaybeString, #[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 tuning_shards: MaybeString,
pub logging_dontLogNull: i32, #[yaserde(rename = "logging_dontLogNull")]
pub logging_dontLogNormal: i32, pub logging_dont_log_null: i32,
pub logging_logSeparateErrors: i32, #[yaserde(rename = "logging_dontLogNormal")]
pub logging_detailedLog: i32, pub logging_dont_log_normal: i32,
pub logging_socketStats: 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_pattern: MaybeString,
pub stickiness_dataTypes: MaybeString, #[yaserde(rename = "stickiness_dataTypes")]
pub stickiness_data_types: MaybeString,
pub stickiness_expire: String, pub stickiness_expire: String,
pub stickiness_size: String, pub stickiness_size: String,
pub stickiness_counter: i32, pub stickiness_counter: i32,
pub stickiness_counter_key: String, pub stickiness_counter_key: String,
pub stickiness_length: MaybeString, pub stickiness_length: MaybeString,
pub stickiness_connRatePeriod: String, #[yaserde(rename = "stickiness_connRatePeriod")]
pub stickiness_sessRatePeriod: String, pub stickiness_conn_rate_period: String,
pub stickiness_httpReqRatePeriod: String, #[yaserde(rename = "stickiness_sessRatePeriod")]
pub stickiness_httpErrRatePeriod: String, pub stickiness_sess_rate_period: String,
pub stickiness_bytesInRatePeriod: String, #[yaserde(rename = "stickiness_httpReqRatePeriod")]
pub stickiness_bytesOutRatePeriod: String, pub stickiness_http_req_rate_period: String,
pub http2Enabled: i32, #[yaserde(rename = "stickiness_httpErrRatePeriod")]
pub http2Enabled_nontls: i32, 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 advertised_protocols: String,
pub forwardFor: i32, #[yaserde(rename = "forwardFor")]
pub forward_for: i32,
pub prometheus_enabled: i32, pub prometheus_enabled: i32,
pub prometheus_path: String, pub prometheus_path: String,
pub connectionBehaviour: String, #[yaserde(rename = "connectionBehaviour")]
pub customOptions: MaybeString, pub connection_behaviour: String,
pub linkedActions: MaybeString, #[yaserde(rename = "customOptions")]
pub linkedErrorfiles: MaybeString, 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)] #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
@ -1628,29 +1729,19 @@ pub struct HAProxyServers {
pub struct HAProxyServer { pub struct HAProxyServer {
#[yaserde(attribute, rename = "uuid")] #[yaserde(attribute, rename = "uuid")]
pub uuid: String, pub uuid: String,
#[yaserde(rename = "id")]
pub id: String, pub id: String,
#[yaserde(rename = "enabled")]
pub enabled: u8, pub enabled: u8,
#[yaserde(rename = "name")]
pub name: String, pub name: String,
#[yaserde(rename = "description")]
pub description: MaybeString, pub description: MaybeString,
#[yaserde(rename = "address")]
pub address: String, pub address: String,
#[yaserde(rename = "port")]
pub port: u16, pub port: u16,
#[yaserde(rename = "checkport")]
pub checkport: MaybeString, pub checkport: MaybeString,
#[yaserde(rename = "mode")]
pub mode: String, pub mode: String,
#[yaserde(rename = "multiplexer_protocol")]
pub multiplexer_protocol: String, pub multiplexer_protocol: String,
#[yaserde(rename = "type")] #[yaserde(rename = "type")]
pub server_type: String, pub server_type: String,
#[yaserde(rename = "serviceName")] #[yaserde(rename = "serviceName")]
pub service_name: MaybeString, pub service_name: MaybeString,
#[yaserde(rename = "number")]
pub number: MaybeString, pub number: MaybeString,
#[yaserde(rename = "linkedResolver")] #[yaserde(rename = "linkedResolver")]
pub linked_resolver: MaybeString, pub linked_resolver: MaybeString,
@ -1658,7 +1749,6 @@ pub struct HAProxyServer {
pub resolver_opts: MaybeString, pub resolver_opts: MaybeString,
#[yaserde(rename = "resolvePrefer")] #[yaserde(rename = "resolvePrefer")]
pub resolve_prefer: MaybeString, pub resolve_prefer: MaybeString,
#[yaserde(rename = "ssl")]
pub ssl: u8, pub ssl: u8,
#[yaserde(rename = "sslSNI")] #[yaserde(rename = "sslSNI")]
pub ssl_sni: MaybeString, pub ssl_sni: MaybeString,
@ -1672,15 +1762,12 @@ pub struct HAProxyServer {
pub ssl_client_certificate: MaybeString, pub ssl_client_certificate: MaybeString,
#[yaserde(rename = "maxConnections")] #[yaserde(rename = "maxConnections")]
pub max_connections: MaybeString, pub max_connections: MaybeString,
#[yaserde(rename = "weight")]
pub weight: u32, pub weight: u32,
#[yaserde(rename = "checkInterval")] #[yaserde(rename = "checkInterval")]
pub check_interval: MaybeString, pub check_interval: MaybeString,
#[yaserde(rename = "checkDownInterval")] #[yaserde(rename = "checkDownInterval")]
pub check_down_interval: MaybeString, pub check_down_interval: MaybeString,
#[yaserde(rename = "source")]
pub source: MaybeString, pub source: MaybeString,
#[yaserde(rename = "advanced")]
pub advanced: MaybeString, pub advanced: MaybeString,
#[yaserde(rename = "unix_socket")] #[yaserde(rename = "unix_socket")]
pub unix_socket: MaybeString, pub unix_socket: MaybeString,

View File

@ -0,0 +1,3 @@
mod xml_utils;
mod data;
pub use data::*;

View File

@ -118,7 +118,7 @@ impl YaSerializeTrait for RawXml {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::infra::yaserde::to_xml_str; use crate::xml_utils::to_xml_str;
use super::*; use super::*;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;

View File

@ -0,0 +1,6 @@
mod generic_xml;
mod maybe_string;
mod yaserde;
pub use generic_xml::*;
pub use maybe_string::*;
pub use yaserde::*;

View File

@ -1,7 +1,9 @@
[package] [package]
name = "opnsense-config" name = "opnsense-config"
version = "0.1.0"
edition = "2021" edition = "2021"
version.workspace = true
readme.workspace = true
license.workspace = true
[dependencies] [dependencies]
serde = { version = "1.0.123", features = [ "derive" ] } serde = { version = "1.0.123", features = [ "derive" ] }
@ -9,14 +11,10 @@ log = { workspace = true }
env_logger = { workspace = true } env_logger = { workspace = true }
russh = { workspace = true } russh = { workspace = true }
russh-keys = { 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" thiserror = "1.0"
async-trait = { workspace = true } async-trait = { workspace = true }
tokio = { workspace = true } tokio = { workspace = true }
opnsense-config-xml = { path = "../opnsense-config-xml" }
[dev-dependencies] [dev-dependencies]
pretty_assertions = "1.4.1" pretty_assertions = "1.4.1"

View File

@ -1,10 +1,10 @@
use crate::error::Error; use crate::error::Error;
use crate::modules::opnsense::OPNsense;
use async_trait::async_trait; 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::client::{Config as SshConfig, Handler};
use russh_keys::key; use russh_keys::key;
use std::{fs, net::Ipv4Addr, path::Path, sync::Arc}; use std::{fs, net::Ipv4Addr, sync::Arc};
#[async_trait] #[async_trait]
pub trait ConfigRepository: std::fmt::Debug { pub trait ConfigRepository: std::fmt::Debug {
@ -87,6 +87,7 @@ impl ConfigRepository for SshConfigRepository {
.await?; .await?;
let mut channel = ssh.channel_open_session().await?; let mut channel = ssh.channel_open_session().await?;
todo!("Backup, Validate, Reload config file");
let command = format!( let command = format!(
"echo '{}' > /conf/config.xml", "echo '{}' > /conf/config.xml",
@ -144,9 +145,9 @@ pub struct Config {
impl Config { impl Config {
pub async fn new(repository: Box<dyn ConfigRepository + Send + Sync>) -> Result<Self, Error> { pub async fn new(repository: Box<dyn ConfigRepository + Send + Sync>) -> Result<Self, Error> {
let xml = repository.load().await?; 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 { Ok(Self {
opnsense, opnsense,
@ -163,15 +164,12 @@ impl Config {
} }
pub async fn save(&self) -> Result<(), Error> { 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(&self.opnsense.to_xml()).await
self.repository.save(&xml).await
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::infra::yaserde::to_xml_str;
use super::*; use super::*;
use std::path::PathBuf; use std::path::PathBuf;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
@ -191,11 +189,33 @@ mod tests {
println!("Config {:?}", config); println!("Config {:?}", config);
let serialized = to_xml_str(&config.opnsense).unwrap(); let serialized = config.opnsense.to_xml();
fs::write("/tmp/serialized.xml", &serialized).unwrap(); fs::write("/tmp/serialized.xml", &serialized).unwrap();
assert_eq!(config_file_str, serialized); 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!();
}
} }

View File

@ -1,4 +0,0 @@
pub mod generic_xml;
pub mod maybe_string;
pub mod yaserde;

View File

@ -1,7 +1,23 @@
pub mod config; pub mod config;
pub mod modules;
pub mod error; pub mod error;
pub mod infra; pub mod modules;
use std::net::Ipv4Addr;
pub use config::Config; pub use config::Config;
pub use error::Error; 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;
}

View File

@ -1,111 +0,0 @@
use xml::reader::XmlEvent;
use yaserde::{YaDeserialize, YaSerialize};
use crate::modules::opnsense::{Interface, Interfaces};
impl YaSerialize for Interfaces {
fn serialize<W: std::io::Write>(
&self,
writer: &mut yaserde::ser::Serializer<W>,
) -> Result<(), String> {
writer.write("Interfaces serializer TODO");
Ok(())
}
fn serialize_attributes(
&self,
attributes: Vec<xml::attribute::OwnedAttribute>,
namespace: xml::namespace::Namespace,
) -> Result<
(
Vec<xml::attribute::OwnedAttribute>,
xml::namespace::Namespace,
),
String,
> {
todo!()
}
}
impl YaDeserialize for Interfaces {
fn deserialize<R: std::io::Read>(
reader: &mut yaserde::de::Deserializer<R>,
) -> Result<Self, String> {
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 = <Interface as yaserde::YaDeserialize>::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)
}
}

View File

@ -1,3 +0,0 @@
mod interfaces;

View File

@ -1,12 +1,9 @@
use opnsense_config_xml::Range;
use opnsense_config_xml::StaticMap;
use std::cmp::Ordering; use std::cmp::Ordering;
use std::net::IpAddr;
use std::net::Ipv4Addr; use std::net::Ipv4Addr;
use super::opnsense::{OPNsense, StaticMap}; use opnsense_config_xml::OPNsense;
use crate::infra::maybe_string::MaybeString;
use crate::modules::opnsense::NumberOption;
use crate::modules::opnsense::Range;
use yaserde_derive::{YaDeserialize, YaSerialize};
pub struct DhcpConfig<'a> { pub struct DhcpConfig<'a> {
opnsense: &'a mut OPNsense, opnsense: &'a mut OPNsense,
@ -48,41 +45,48 @@ impl<'a> DhcpConfig<'a> {
pub fn add_static_mapping( pub fn add_static_mapping(
&mut self, &mut self,
mac: String, mac: &str,
ipaddr: Ipv4Addr, ipaddr: Ipv4Addr,
hostname: String, hostname: &str,
) -> Result<(), DhcpError> { ) -> 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) { if !Self::is_valid_mac(&mac) {
return Err(DhcpError::InvalidMacAddress(mac)); return Err(DhcpError::InvalidMacAddress(mac));
} }
// if !Self::is_ip_in_range(&ipaddr, range) { if !Self::is_ip_in_range(&ipaddr, range) {
// return Err(DhcpError::IpAddressOutOfRange(ipaddr)); return Err(DhcpError::IpAddressOutOfRange(ipaddr.to_string()));
// } }
let existing_mappings = &self.opnsense.dhcpd.lan.staticmaps; let existing_mappings = &self.opnsense.dhcpd.lan.staticmaps;
// TODO if existing_mappings.iter().any(|m| {
// if existing_mappings.iter().any(|m| m.ipaddr == ipaddr) { m.ipaddr
// return Err(DhcpError::IpAddressAlreadyMapped(ipaddr)); .parse::<Ipv4Addr>()
// } .expect("Mapping contains invalid ipv4")
== ipaddr
}) {
return Err(DhcpError::IpAddressAlreadyMapped(ipaddr.to_string()));
}
// if existing_mappings.iter().any(|m| m.mac == mac) { if existing_mappings.iter().any(|m| m.mac == mac) {
// return Err(DhcpError::MacAddressAlreadyMapped(mac)); return Err(DhcpError::MacAddressAlreadyMapped(mac));
// } }
// let static_map = StaticMap { let static_map = StaticMap {
// mac, mac,
// ipaddr, ipaddr: ipaddr.to_string(),
// hostname, hostname,
// descr: Default::default(), descr: Default::default(),
// winsserver: Default::default(), winsserver: Default::default(),
// dnsserver: Default::default(), dnsserver: Default::default(),
// ntpserver: Default::default(), ntpserver: Default::default(),
// }; };
// self.opnsense.dhcpd.lan.staticmaps.push(static_map); self.opnsense.dhcpd.lan.staticmaps.push(static_map);
Ok(()) Ok(())
} }
@ -101,7 +105,7 @@ impl<'a> DhcpConfig<'a> {
.all(|part| part.len() == 2 && part.chars().all(|c| c.is_ascii_hexdigit())) .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 let range_start = range
.from .from
.parse::<Ipv4Addr>() .parse::<Ipv4Addr>()
@ -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<NumberOption>,
#[yaserde(rename = "range")]
pub range: Range,
pub winsserver: MaybeString,
pub dnsserver: MaybeString,
pub ntpserver: MaybeString,
#[yaserde(rename = "staticmap")]
pub staticmaps: Vec<StaticMap>,
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)] #[cfg(test)]
mod test { mod test {
use std::net::Ipv4Addr;
use crate::infra::yaserde::to_xml_str;
use super::*; use super::*;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use std::net::Ipv4Addr;
#[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 = "<?xml version=\"1.0\"?>
<dhcpd>
<lan>
<enable>1</enable>
<gateway>192.168.20.1</gateway>
<domain>somedomain.yourlocal.mcd</domain>
<ddnsdomainalgorithm>hmac-md5</ddnsdomainalgorithm>
<numberoptions>
<item/>
</numberoptions>
<range>
<from>192.168.20.50</from>
<to>192.168.20.200</to>
</range>
<winsserver/>
<dnsserver>192.168.20.1</dnsserver>
<ntpserver/>
<staticmap>
<mac>55:55:55:55:55:1c</mac>
<ipaddr>192.168.20.160</ipaddr>
<hostname>somehost983</hostname>
<descr>someservire8</descr>
<winsserver/>
<dnsserver/>
<ntpserver/>
</staticmap>
<staticmap>
<mac>55:55:55:55:55:1c</mac>
<ipaddr>192.168.20.155</ipaddr>
<hostname>somehost893</hostname>
<winsserver/>
<dnsserver/>
<ntpserver/>
</staticmap>
<staticmap>
<mac>55:55:55:55:55:1c</mac>
<ipaddr>192.168.20.165</ipaddr>
<hostname>somehost893</hostname>
<descr/>
<winsserver/>
<dnsserver/>
<ntpserver/>
</staticmap>
<staticmap>
<mac>55:55:55:55:55:1c</mac>
<ipaddr>192.168.20.50</ipaddr>
<hostname>hostswitch2</hostname>
<descr>switch-2 (bottom)</descr>
<winsserver/>
<dnsserver/>
<ntpserver/>
</staticmap>
<pool/>
</lan>
</dhcpd>\n";
#[test] #[test]
fn test_ip_in_range() { fn test_ip_in_range() {
@ -239,22 +140,23 @@ mod test {
// Test IP within range // Test IP within range
let ip = "192.168.1.150".parse::<Ipv4Addr>().unwrap(); let ip = "192.168.1.150".parse::<Ipv4Addr>().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 // Test IP at start of range
let ip = "192.168.1.100".parse::<Ipv4Addr>().unwrap(); let ip = "192.168.1.100".parse::<Ipv4Addr>().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 // Test IP at end of range
let ip = "192.168.1.200".parse::<Ipv4Addr>().unwrap(); let ip = "192.168.1.200".parse::<Ipv4Addr>().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 // Test IP before range
let ip = "192.168.1.99".parse::<Ipv4Addr>().unwrap(); let ip = "192.168.1.99".parse::<Ipv4Addr>().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 // Test IP after range
let ip = "192.168.1.201".parse::<Ipv4Addr>().unwrap(); let ip = "192.168.1.201".parse::<Ipv4Addr>().unwrap();
assert_eq!(DhcpConfig::is_ip_in_range(&ip, range.clone()), false); assert_eq!(DhcpConfig::is_ip_in_range(&ip, &range), false);
} }
} }

View File

@ -1,3 +1 @@
pub mod opnsense;
pub mod dhcp; pub mod dhcp;
mod deserializer;