Merge pull request 'feat/opnsenseDNS' (#6) from feat/opnsenseDNS into master

Reviewed-on: https://git.nationtech.io/NationTech/harmony/pulls/6
This commit is contained in:
johnride 2024-12-18 14:21:54 +00:00
commit 58f81f0e58
20 changed files with 468 additions and 154 deletions

51
harmony-rs/Cargo.lock generated
View File

@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
version = 4
[[package]]
name = "addr2line"
@ -152,7 +152,7 @@ checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.77",
"syn 2.0.90",
]
[[package]]
@ -471,7 +471,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.77",
"syn 2.0.90",
]
[[package]]
@ -499,7 +499,7 @@ checksum = "2cdc8d50f426189eef89dac62fabfa0abb27d5cc008f25bf4156a0203325becc"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.77",
"syn 2.0.90",
]
[[package]]
@ -784,7 +784,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.77",
"syn 2.0.90",
]
[[package]]
@ -905,6 +905,15 @@ dependencies = [
"tokio",
]
[[package]]
name = "harmony_macros"
version = "1.0.0"
dependencies = [
"harmony",
"quote",
"syn 2.0.90",
]
[[package]]
name = "hashbrown"
version = "0.14.5"
@ -1346,7 +1355,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.77",
"syn 2.0.90",
]
[[package]]
@ -1620,9 +1629,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.86"
version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
dependencies = [
"unicode-ident",
]
@ -2062,7 +2071,7 @@ checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.77",
"syn 2.0.90",
]
[[package]]
@ -2247,9 +2256,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.77"
version = "2.0.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed"
checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
dependencies = [
"proc-macro2",
"quote",
@ -2319,7 +2328,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.77",
"syn 2.0.90",
]
[[package]]
@ -2371,7 +2380,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.77",
"syn 2.0.90",
]
[[package]]
@ -2542,7 +2551,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.77",
"syn 2.0.90",
"wasm-bindgen-shared",
]
@ -2576,7 +2585,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.77",
"syn 2.0.90",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@ -2786,6 +2795,16 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "wk"
version = "0.1.0"
dependencies = [
"cidr",
"harmony",
"harmony_macros",
"tokio",
]
[[package]]
name = "wyz"
version = "0.5.1"
@ -2845,7 +2864,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.77",
"syn 2.0.90",
]
[[package]]

View File

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

View File

@ -96,10 +96,10 @@ pub enum StorageKind {
}
#[derive(Debug, new, Clone)]
pub struct Storage {
connection: StorageConnectionType,
kind: StorageKind,
size: u64,
serial: String,
pub connection: StorageConnectionType,
pub kind: StorageKind,
pub size: u64,
pub serial: String,
}
#[derive(Debug, Clone)]

View File

@ -7,7 +7,6 @@ use super::{
data::{Id, Version},
executors::ExecutorError,
inventory::Inventory,
score::Score,
topology::HAClusterTopology,
};
@ -41,9 +40,13 @@ pub struct Outcome {
status: InterpretStatus,
message: String,
}
impl std::fmt::Display for Outcome {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("Outcome {}: {}", self.status, self.message))
impl Outcome {
pub fn noop() -> Self {
Self {
status: InterpretStatus::NOOP,
message: String::new(),
}
}
}
@ -54,6 +57,7 @@ pub enum InterpretStatus {
RUNNING,
QUEUED,
BLOCKED,
NOOP,
}
impl std::fmt::Display for InterpretStatus {
@ -64,6 +68,7 @@ impl std::fmt::Display for InterpretStatus {
InterpretStatus::RUNNING => "RUNNING",
InterpretStatus::QUEUED => "QUEUED",
InterpretStatus::BLOCKED => "BLOCKED",
InterpretStatus::NOOP => "NO_OP",
};
f.write_str(msg)
}

View File

@ -34,7 +34,79 @@ pub type IpAddress = IpAddr;
/// This abstraction focuses on the logical role and services, independent of the physical hardware.
#[derive(Debug, Clone)]
pub struct LogicalHost {
/// The set of services this logical host provides
/// The IP address of this logical host.
pub ip: IpAddress,
/// The name of this logical host.
pub name: String,
}
impl LogicalHost {
/// Creates a list of `LogicalHost` instances.
///
/// # Arguments
///
/// * `number_hosts` - The number of logical hosts to create.
/// * `start_ip` - The starting IP address. Each subsequent host's IP will be incremented.
/// * `hostname_prefix` - The prefix for the host names. Host names will be in the form `prefix<index>`.
///
/// # Returns
///
/// A `Vec<LogicalHost>` containing the specified number of logical hosts, each with a unique IP and name.
///
/// # Panics
///
/// This function will panic if adding `number_hosts` to `start_ip` exceeds the valid range of IP addresses.
///
/// # Examples
///
/// ```
/// use std::str::FromStr;
/// use harmony::topology::{IpAddress, LogicalHost};
///
/// let start_ip = IpAddress::from_str("192.168.0.20").unwrap();
/// let hosts = LogicalHost::create_hosts(3, start_ip, "worker");
///
/// assert_eq!(hosts.len(), 3);
/// assert_eq!(hosts[0].ip, IpAddress::from_str("192.168.0.20").unwrap());
/// assert_eq!(hosts[0].name, "worker0");
/// assert_eq!(hosts[1].ip, IpAddress::from_str("192.168.0.21").unwrap());
/// assert_eq!(hosts[1].name, "worker1");
/// assert_eq!(hosts[2].ip, IpAddress::from_str("192.168.0.22").unwrap());
/// assert_eq!(hosts[2].name, "worker2");
/// ```
pub fn create_hosts(number_hosts: u32, start_ip: IpAddress, hostname_prefix: &str) -> Vec<LogicalHost> {
let mut hosts = Vec::with_capacity(number_hosts.try_into().unwrap());
for i in 0..number_hosts {
let new_ip = increment_ip(start_ip, i).expect("IP address overflow");
let name = format!("{}{}", hostname_prefix, i);
hosts.push(LogicalHost { ip: new_ip, name });
}
hosts
}
}
/// Increments an IP address by a given value.
///
/// # Arguments
///
/// * `ip` - The starting IP address.
/// * `increment` - The amount to add to the IP address.
///
/// # Returns
///
/// A new `IpAddress` that is the result of incrementing the original by `increment`.
///
/// # Panics
///
/// This function panics if the resulting IP address exceeds the valid range.
fn increment_ip(ip: IpAddress, increment: u32) -> Option<IpAddress> {
match ip {
IpAddress::V4(ipv4) => {
let new_ip = u32::from(ipv4) + increment;
Some(IpAddress::V4(new_ip.into()))
}
IpAddress::V6(_) => {
todo!("Ipv6 not supported yet")
}
}
}

View File

@ -45,8 +45,10 @@ pub trait DhcpServer: Send + Sync {
async fn add_static_mapping(&self, entry: &DHCPStaticEntry) -> Result<(), ExecutorError>;
async fn remove_static_mapping(&self, mac: &MacAddress) -> Result<(), ExecutorError>;
async fn list_static_mappings(&self) -> Vec<(MacAddress, IpAddress)>;
async fn set_next_server(&self, ip: IpAddress) -> Result<(), ExecutorError>;
fn get_ip(&self) -> IpAddress;
fn get_host(&self) -> LogicalHost;
async fn commit_config(&self) -> Result<(), ExecutorError>;
}
impl std::fmt::Debug for dyn DhcpServer {

View File

@ -3,9 +3,7 @@ use derive_new::new;
use crate::{hardware::ManagementInterface, topology::MacAddress};
#[derive(new)]
pub struct OPNSenseManagementInterface {
mac: MacAddress,
}
pub struct OPNSenseManagementInterface {}
impl ManagementInterface for OPNSenseManagementInterface {
fn boot_to_pxe(&self) {
@ -13,7 +11,7 @@ impl ManagementInterface for OPNSenseManagementInterface {
}
fn get_mac_address(&self) -> MacAddress {
self.mac.clone()
todo!("OPNSense can have multiple mac addresses using SSH. I'm not sure it even belongs in the ManagementInterface trait")
}
fn get_supported_protocol_names(&self) -> String {

View File

@ -1,9 +1,10 @@
mod management;
use std::sync::{Arc, Mutex, RwLock, RwLockWriteGuard};
use std::sync::Arc;
use async_trait::async_trait;
use log::debug;
pub use management::*;
use tokio::sync::RwLock;
use crate::{
executors::ExecutorError,
@ -97,14 +98,24 @@ impl LoadBalancer for OPNSenseFirewall {
#[async_trait]
impl DhcpServer for OPNSenseFirewall {
async fn commit_config(&self) -> Result<(), ExecutorError> {
self.opnsense_config
.read()
.await
.apply()
.await
.map_err(|e| ExecutorError::UnexpectedError(e.to_string()))
}
async fn add_static_mapping(&self, entry: &DHCPStaticEntry) -> Result<(), ExecutorError> {
let mac: String = String::from(&entry.mac);
{
let mut writable_opnsense = self.opnsense_config.write().unwrap();
let mut writable_opnsense = self.opnsense_config.write().await;
writable_opnsense
.dhcp()
.add_static_mapping(&mac, entry.ip, &entry.name).unwrap();
.add_static_mapping(&mac, entry.ip, &entry.name)
.unwrap();
}
debug!("Registered {:?}", entry);
@ -125,9 +136,24 @@ impl DhcpServer for OPNSenseFirewall {
fn get_ip(&self) -> IpAddress {
OPNSenseFirewall::get_ip(self)
}
fn get_host(&self) -> LogicalHost {
self.host.clone()
}
async fn set_next_server(&self, ip: IpAddress) -> Result<(), ExecutorError> {
let ipv4 = match ip {
std::net::IpAddr::V4(ipv4_addr) => ipv4_addr,
std::net::IpAddr::V6(_) => todo!("ipv6 not supported yet"),
};
{
let mut writable_opnsense = self.opnsense_config.write().await;
writable_opnsense.dhcp().set_next_server(ipv4);
debug!("OPNsense dhcp server set next server {ipv4}");
}
Ok(())
}
}
impl DnsServer for OPNSenseFirewall {

View File

@ -2,3 +2,8 @@ mod domain;
pub use domain::*;
pub mod infra;
pub mod modules;
#[cfg(test)]
mod test {
use crate::infra::opnsense::OPNSenseFirewall;
}

View File

@ -1,4 +1,4 @@
use std::{net::Ipv4Addr, sync::Arc};
use std::sync::Arc;
use async_trait::async_trait;
use derive_new::new;
@ -11,50 +11,15 @@ use crate::{
},
interpret::{Interpret, InterpretError, InterpretName, Outcome},
inventory::Inventory,
topology::{DHCPStaticEntry, HAClusterTopology, HostBinding},
topology::{DHCPStaticEntry, HAClusterTopology, HostBinding, IpAddress},
};
use crate::domain::score::Score;
/// OPNSenseDhcpScore will set static DHCP entries using index based hostname
/// and ip addresses.
///
/// For example :
/// ```rust
///
/// let node1 = todo!(); // Node pointing to clustermember controlplane0 with ip 10.10.0.20 and host with mac 01
/// let node2 = todo!(); // Node pointing to clustermember controlplane1 with ip 10.10.0.21 and host with mac 02
/// let node3 = todo!(); // Node pointing to clustermember controlplane2 with ip 10.10.0.22 and host with mac 03
///
/// let score = OPNSenseDhcpScore {
/// nodes: vec![node1, node2, node3],
/// }
/// ```
///
/// Running such a score would create these static entries :
///
/// ```rust
/// let entries = vec![
/// DHCPEntry {
/// mac: 01,
/// ip: 10.10.0.20,
/// hostname: "controlplane0"
/// }
/// DHCPEntry {
/// mac: 02,
/// ip: 10.10.0.21,
/// hostname: "controlplane0"
/// }
/// DHCPEntry {
/// mac: 03,
/// ip: 10.10.0.22,
/// hostname: "controlplane2"
/// }
/// ]
/// ```
#[derive(Debug, new, Clone)]
pub struct DhcpScore {
host_binding: Vec<HostBinding>,
next_server: Option<IpAddress>,
}
impl Score for DhcpScore {
@ -78,7 +43,7 @@ pub struct DhcpInterpret {
impl DhcpInterpret {
pub fn new(score: DhcpScore) -> Self {
let version = Version::from("1.0.0").expect("Version should be valid");
let name = "OPNSenseDhcpScore".to_string();
let name = "DhcpInterpret".to_string();
let id = Id::from_string(format!("{name}_{version}"));
Self {
@ -89,6 +54,67 @@ impl DhcpInterpret {
status: InterpretStatus::QUEUED,
}
}
async fn add_static_entries(
&self,
_inventory: &Inventory,
topology: &HAClusterTopology,
) -> Result<Outcome, InterpretError> {
let dhcp_entries: Vec<DHCPStaticEntry> = self
.score
.host_binding
.iter()
.map(|binding| {
let ip = match binding.logical_host.ip {
std::net::IpAddr::V4(ipv4) => ipv4,
std::net::IpAddr::V6(_) => {
unimplemented!("DHCPStaticEntry only supports ipv4 at the moment")
}
};
DHCPStaticEntry {
name: binding.logical_host.name.clone(),
mac: binding.physical_host.cluster_mac(),
ip,
}
})
.collect();
info!("DHCPStaticEntry : {:?}", dhcp_entries);
let dhcp_server = Arc::new(topology.dhcp_server.clone());
info!("DHCP server : {:?}", dhcp_server);
let number_new_entries = dhcp_entries.len();
for entry in dhcp_entries.into_iter() {
match dhcp_server.add_static_mapping(&entry).await {
Ok(_) => info!("Successfully registered DHCPStaticEntry {}", entry),
Err(_) => todo!(),
}
}
Ok(Outcome::new(
InterpretStatus::SUCCESS,
format!("Dhcp Interpret registered {} entries", number_new_entries),
))
}
async fn set_next_server(
&self,
_inventory: &Inventory,
topology: &HAClusterTopology,
) -> Result<Outcome, InterpretError> {
match self.score.next_server {
Some(next_server) => {
let dhcp_server = Arc::new(topology.dhcp_server.clone());
dhcp_server.set_next_server(next_server).await?;
Ok(Outcome::new(
InterpretStatus::SUCCESS,
format!("Dhcp Interpret Set next boot to {next_server}"),
))
}
None => Ok(Outcome::noop()),
}
}
}
#[async_trait]
@ -116,63 +142,15 @@ impl Interpret for DhcpInterpret {
) -> Result<Outcome, InterpretError> {
info!("Executing {} on inventory {inventory:?}", self.get_name());
let dhcp_entries: Vec<DHCPStaticEntry> = self
.score
.host_binding
.iter()
.map(|binding| {
let ip = match binding.logical_host.ip {
std::net::IpAddr::V4(ipv4) => ipv4,
std::net::IpAddr::V6(_) => unimplemented!("DHCPStaticEntry only supports ipv4 at the moment"),
};
self.set_next_server(inventory, topology).await?;
DHCPStaticEntry {
name: binding.logical_host.name.clone(),
mac: binding.physical_host.cluster_mac(),
ip,
}
})
.collect();
info!("DHCPStaticEntry : {:?}", dhcp_entries);
// self.add_static_entries(inventory, topology).await?;
let dhcp = Arc::new(Box::new(topology.dhcp_server.clone()));
info!("DHCP server : {:?}", dhcp);
for entry in dhcp_entries.into_iter() {
match dhcp.add_static_mapping(&entry).await {
Ok(_) => info!("Successfully registered DHCPStaticEntry {}", entry),
Err(_) => todo!(),
}
}
todo!("Configure DHCPServer");
topology.dhcp_server.commit_config().await?;
Ok(Outcome::new(
InterpretStatus::SUCCESS,
"Connection test successful".to_string(),
format!("Dhcp Interpret execution successful"),
))
}
}
#[cfg(test)]
mod test {
#[test]
fn opnsense_dns_score_should_do_nothing_on_empty_inventory() {
todo!();
}
#[test]
fn opnsense_dns_score_should_set_entry_for_bootstrap_node() {
todo!();
}
#[test]
fn opnsense_dns_score_should_set_entry_for_control_plane_members() {
todo!();
}
#[test]
fn opnsense_dns_score_should_set_entry_for_workers() {
todo!();
}
}

View File

@ -1 +1,2 @@
pub mod dhcp;
pub mod okd;

View File

@ -0,0 +1,45 @@
use crate::{
inventory::Inventory,
modules::dhcp::DhcpScore,
score::Score,
topology::{HAClusterTopology, HostBinding},
};
#[derive(Debug)]
pub struct OKDBootstrapDhcpScore {
dhcp_score: DhcpScore,
}
impl OKDBootstrapDhcpScore {
pub fn new(topology: &HAClusterTopology, inventory: &Inventory) -> Self {
let host_binding = topology
.control_plane
.iter()
.enumerate()
.map(|(index, topology_entry)| HostBinding {
logical_host: topology_entry.clone(),
physical_host: inventory
.control_plane_host
.get(index)
.expect("Iventory should contain at least as many physical hosts as topology")
.clone(),
})
.collect();
Self {
dhcp_score: DhcpScore::new(
host_binding,
// TODO : we should add a tftp server to the topology instead of relying on the
// router address, this is leaking implementation details
Some(topology.router.get_gateway()),
),
}
}
}
impl Score for OKDBootstrapDhcpScore {
type InterpretType = <DhcpScore as Score>::InterpretType;
fn create_interpret(self) -> Self::InterpretType {
self.dhcp_score.create_interpret()
}
}

View File

@ -0,0 +1,2 @@
pub mod dhcp;

46
harmony-rs/harmony_macros/Cargo.lock generated Normal file
View File

@ -0,0 +1,46 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "harmony_macros"
version = "1.0.0"
dependencies = [
"quote",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [
"proc-macro2",
]
[[package]]
name = "syn"
version = "2.0.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"

View File

@ -0,0 +1,12 @@
[package]
name = "harmony_macros"
edition = "2024"
version = "1.0.0"
[lib]
proc-macro = true
[dependencies]
harmony = { version = "0.1.0", path = "../harmony" }
quote = "1.0.37"
syn = "2.0.90"

View File

@ -0,0 +1,66 @@
extern crate proc_macro;
use harmony::topology::MacAddress;
use proc_macro::TokenStream;
use quote::quote;
use syn::{LitStr, parse_macro_input};
#[proc_macro]
pub fn ip(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as LitStr);
let ip_str = input.value();
if let Ok(_) = ip_str.parse::<std::net::Ipv4Addr>() {
let expanded =
quote! { std::net::IpAddr::V4(#ip_str.parse::<std::net::Ipv4Addr>().unwrap()) };
return TokenStream::from(expanded);
}
if let Ok(_) = ip_str.parse::<std::net::Ipv6Addr>() {
let expanded =
quote! { std::net::IpAddr::V4(#ip_str.parse::<std::net::Ipv6Addr>().unwrap()) };
return TokenStream::from(expanded);
}
panic!("Invalid IP address: {}", ip_str);
}
#[proc_macro]
pub fn mac_address(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as LitStr);
let mac_str = input.value();
match parse_mac_address(&mac_str) {
Ok(bytes) => {
let b0 = bytes[0];
let b1 = bytes[1];
let b2 = bytes[2];
let b3 = bytes[3];
let b4 = bytes[4];
let b5 = bytes[5];
quote! {
MacAddress( [#b0, #b1, #b2, #b3, #b4, #b5] )
}
.into()
}
Err(err) => syn::Error::new(input.span(), err).to_compile_error().into(),
}
}
fn parse_mac_address(mac: &str) -> Result<[u8; 6], String> {
let parts: Vec<&str> = mac.split(':').collect();
if parts.len() != 6 {
return Err("MAC address must contain exactly six octets separated by colons".to_string());
}
let mut bytes = [0u8; 6];
for (i, part) in parts.iter().enumerate() {
match u8::from_str_radix(part, 16) {
Ok(byte) => bytes[i] = byte,
Err(_) => return Err(format!("Invalid MAC address octet: {}", part)),
}
}
Ok(bytes)
}

View File

@ -16,6 +16,8 @@ pub struct DhcpInterface {
pub enable: Option<MaybeString>,
pub gateway: Option<MaybeString>,
pub domain: Option<MaybeString>,
pub netboot: Option<u32>,
pub nextserver: Option<String>,
#[yaserde(rename = "ddnsdomainalgorithm")]
pub ddns_domain_algorithm: Option<MaybeString>,
#[yaserde(rename = "numberoptions")]

View File

@ -27,7 +27,7 @@ pub struct OPNsense {
pub opnsense: OPNsenseXmlSection,
pub staticroutes: StaticRoutes,
pub ca: MaybeString,
pub gateways: Option<Gateways>,
pub gateways: Option<RawXml>,
pub cert: Vec<Cert>,
pub dhcpdv6: DhcpDv6,
pub virtualip: VirtualIp,
@ -60,7 +60,6 @@ impl OPNsense {
}
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct LoadBalancer {
pub monitor_type: Vec<MonitorType>,
@ -415,6 +414,8 @@ pub struct OPNsenseXmlSection {
pub ipsec: Option<IPsec>,
#[yaserde(rename = "Interfaces")]
pub interfaces: Option<ConfigInterfaces>,
#[yaserde(rename = "NodeExporter")]
pub node_exporter: Option<RawXml>,
#[yaserde(rename = "Kea")]
pub kea: Option<RawXml>,
pub monit: Option<Monit>,
@ -428,6 +429,7 @@ pub struct OPNsenseXmlSection {
pub unboundplus: Option<RawXml>,
#[yaserde(rename = "DHCRelay")]
pub dhcrelay: Option<RawXml>,
pub trust: Option<RawXml>,
pub wireguard: Option<Wireguard>,
#[yaserde(rename = "Swanctl")]
pub swanctl: Swanctl,
@ -479,6 +481,8 @@ pub struct IDSGeneral {
#[yaserde(rename = "LogPayload")]
log_payload: Option<u8>,
verbosity: MaybeString,
#[yaserde(rename = "eveLog")]
eve_log: Option<RawXml>,
}
#[derive(Debug, YaSerialize, YaDeserialize, PartialEq)]
@ -498,11 +502,15 @@ pub struct IPsec {
key_pairs: MaybeString,
#[yaserde(rename = "preSharedKeys")]
pre_shared_keys: MaybeString,
charon: Option<RawXml>,
}
#[derive(Debug, YaSerialize, YaDeserialize, PartialEq)]
pub struct GeneralIpsec {
enabled: MaybeString,
preferred_oldsa: MaybeString,
disablevpnrules: MaybeString,
passthrough_networks: MaybeString,
}
#[derive(Debug, YaSerialize, YaDeserialize, PartialEq)]
@ -1214,6 +1222,8 @@ pub struct WireguardServerItem {
pub gateway: MaybeString,
pub carp_depend_on: MaybeString,
pub peers: String,
pub endpoint: MaybeString,
pub peer_dns: MaybeString,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
@ -1320,7 +1330,6 @@ pub struct ConfigOpenVPN {
pub StaticKeys: MaybeString,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
#[yaserde(rename = "HAProxy")]
pub struct HAProxy {
@ -1411,6 +1420,8 @@ pub struct Tuning {
#[yaserde(rename = "maxConnections")]
pub max_connections: MaybeString,
pub nbthread: i32,
#[yaserde(rename = "resolversPrefer")]
pub resolvers_prefer: String,
#[yaserde(rename = "sslServerVerify")]
pub ssl_server_verify: String,
#[yaserde(rename = "maxDHSize")]
@ -1425,6 +1436,12 @@ pub struct Tuning {
pub lua_max_mem: i32,
#[yaserde(rename = "customOptions")]
pub custom_options: MaybeString,
#[yaserde(rename = "ocspUpdateEnabled")]
pub ocs_update_enabled: MaybeString,
#[yaserde(rename = "ocspUpdateMinDelay")]
pub ocs_update_min_delay: MaybeString,
#[yaserde(rename = "ocspUpdateMaxDelay")]
pub ocs_update_max_delay: MaybeString,
#[yaserde(rename = "ssl_defaultsEnabled")]
pub ssl_defaults_enabled: i32,
#[yaserde(rename = "ssl_bindOptions")]
@ -1437,6 +1454,19 @@ pub struct Tuning {
pub ssl_cipher_list: String,
#[yaserde(rename = "ssl_cipherSuites")]
pub ssl_cipher_suites: String,
#[yaserde(rename = "h2_initialWindowSize")]
pub h2_initial_window_size: Option<MaybeString>,
#[yaserde(rename = "h2_initialWindowSizeOutgoing")]
pub h2_initial_window_size_outgoing: Option<MaybeString>,
#[yaserde(rename = "h2_initialWindowSizeIncoming")]
pub h2_initial_window_size_incoming: Option<MaybeString>,
#[yaserde(rename = "h2_maxConcurrentStreams")]
pub h2_max_concurrent_streams: Option<MaybeString>,
#[yaserde(rename = "h2_maxConcurrentStreamsOutgoing")]
pub h2_max_concurrent_streams_outgoing: Option<MaybeString>,
#[yaserde(rename = "h2_maxConcurrentStreamsIncoming")]
pub h2_max_concurrent_streams_incoming: Option<MaybeString>,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
@ -1684,25 +1714,22 @@ pub struct Backend {
pub http2_enabled_nontls: u8,
#[yaserde(rename = "ba_advertised_protocols")]
pub ba_advertised_protocols: String,
#[yaserde(rename = "persistence")]
pub persistence: String,
#[yaserde(rename = "persistence_cookiemode")]
#[yaserde(rename = "forwardFor")]
pub forward_for: Option<i32>,
#[yaserde(rename = "forwardedHeader")]
pub forwarded_header: Option<MaybeString>,
#[yaserde(rename = "forwardedHeaderParameters")]
pub forwarded_header_parameters: Option<MaybeString>,
pub persistence: MaybeString,
pub persistence_cookiemode: String,
#[yaserde(rename = "persistence_cookiename")]
pub persistence_cookiename: MaybeString,
#[yaserde(rename = "persistence_stripquotes")]
pub persistence_stripquotes: u8,
#[yaserde(rename = "stickiness_pattern")]
pub stickiness_pattern: String,
pub stickiness_pattern: MaybeString,
#[yaserde(rename = "stickiness_dataTypes")]
pub stickiness_data_types: MaybeString,
#[yaserde(rename = "stickiness_expire")]
pub stickiness_expire: String,
#[yaserde(rename = "stickiness_size")]
pub stickiness_size: String,
#[yaserde(rename = "stickiness_cookiename")]
pub stickiness_cookiename: MaybeString,
#[yaserde(rename = "stickiness_cookielength")]
pub stickiness_cookielength: MaybeString,
#[yaserde(rename = "stickiness_connRatePeriod")]
pub stickiness_conn_rate_period: String,
@ -1863,12 +1890,6 @@ pub struct StaticRoutes {
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct Ca {}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct Gateways {
#[yaserde(rename = "gateway_item")]
pub gateway_item: RawXml
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct Cert {
#[yaserde(attribute)]
@ -1975,14 +1996,14 @@ pub struct Bridges {
pub struct Gifs {
#[yaserde(attribute)]
pub version: Option<String>,
pub gif: MaybeString,
pub gif: Option<MaybeString>,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct Gres {
#[yaserde(attribute)]
pub version: Option<String>,
pub gre: MaybeString,
pub gre: Option<MaybeString>,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]

View File

@ -24,9 +24,10 @@ impl SshConfigManager {
impl SshConfigManager {
async fn backup_config_remote(&self) -> Result<String, Error> {
let backup_filename = format!("config_{}.xml", chrono::Local::now().format("%Y%m%d%H%M%S"));
let ts = chrono::Utc::now();
let backup_filename = format!("config-{}-harmony.xml", ts.format("%s%.3f"));
self.opnsense_shell.exec(&format!("cp /conf/config.xml /tmp/{}", backup_filename))
self.opnsense_shell.exec(&format!("cp /conf/config.xml /conf/backup/{}", backup_filename))
.await
}

View File

@ -54,7 +54,9 @@ impl<'a> DhcpConfig<'a> {
pub fn remove_static_mapping(&mut self, mac: &str) {
let lan_dhcpd = self.get_lan_dhcpd();
lan_dhcpd.staticmaps.retain(|static_entry| static_entry.mac != mac);
lan_dhcpd
.staticmaps
.retain(|static_entry| static_entry.mac != mac);
}
fn get_lan_dhcpd(&mut self) -> &mut opnsense_config_xml::DhcpInterface {
@ -77,16 +79,19 @@ impl<'a> DhcpConfig<'a> {
let mac = mac.to_string();
let hostname = hostname.to_string();
let lan_dhcpd = self.get_lan_dhcpd();
let range = &lan_dhcpd.range;
let existing_mappings: &mut Vec<StaticMap> = &mut lan_dhcpd.staticmaps;
if !Self::is_valid_mac(&mac) {
return Err(DhcpError::InvalidMacAddress(mac));
}
if !Self::is_ip_in_range(&ipaddr, range) {
return Err(DhcpError::IpAddressOutOfRange(ipaddr.to_string()));
}
// TODO verify if address is in subnet range
// This check here does not do what we want to do, as we want to assign static leases
// outside of the dynamic DHCP pool
// let range = &lan_dhcpd.range;
// if !Self::is_ip_in_range(&ipaddr, range) {
// return Err(DhcpError::IpAddressOutOfRange(ipaddr.to_string()));
// }
if existing_mappings.iter().any(|m| {
m.ipaddr
@ -123,7 +128,7 @@ impl<'a> DhcpConfig<'a> {
parts
.iter()
.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 {
@ -173,6 +178,14 @@ impl<'a> DhcpConfig<'a> {
Ok(static_maps)
}
pub fn enable_netboot(&mut self) {
self.get_lan_dhcpd().netboot = Some(1);
}
pub fn set_next_server(&mut self, ip: Ipv4Addr) {
self.enable_netboot();
self.get_lan_dhcpd().nextserver = Some(ip.to_string());
}
}
#[cfg(test)]