Compare commits

..

14 Commits

Author SHA1 Message Date
c631b3aef9 fix more opnsense stuff, remove installation notes
All checks were successful
Run Check Script / check (pull_request) Successful in 1m19s
2026-01-22 15:54:19 -05:00
3e2d94cff0 adding Cargo.lock
All checks were successful
Run Check Script / check (pull_request) Successful in 1m19s
2026-01-22 14:54:39 -05:00
c9e39d11ad fix: fix opnsense stuff for opnsense 25.1 test file
Some checks failed
Run Check Script / check (pull_request) Failing after 12s
2026-01-22 14:52:29 -05:00
5ed14b75ed chore: fix formatting 2026-01-22 08:47:56 -05:00
25a45096f8 doc: adding installation notes file 2026-01-21 16:22:59 -05:00
74252ded5c chore: remove useless brocade stuff
Some checks failed
Run Check Script / check (pull_request) Failing after 24s
2026-01-21 15:12:23 -05:00
0ecadbfb97 chore: remove unused import
Some checks failed
Run Check Script / check (pull_request) Failing after 23s
2026-01-21 15:07:35 -05:00
eb492f3ca9 fix: remove double definition of RUST_LOG in env.sh
Some checks failed
Run Check Script / check (pull_request) Failing after 19m39s
2026-01-21 14:02:58 -05:00
de3c8e9a41 adding data symlink
Some checks failed
Run Check Script / check (pull_request) Failing after 31s
2026-01-21 13:59:22 -05:00
2ef2d9f064 Fix HostRole (ControlPlane -> Worker) in workers score, fix main, add topology.rs 2026-01-21 13:56:28 -05:00
d2d18205e9 fix deps in Cargo.toml, create env.sh file 2026-01-21 13:09:26 -05:00
0b55a6fb53 fix: add new xml fields after updating opnsense 2026-01-20 14:27:28 -05:00
001dd5269c add (now commented) line to init env_logger 2026-01-18 10:07:28 -05:00
9978acf16d feat: change staticroutes->route to Option<RawXml> instead of MaybeString 2026-01-18 10:06:15 -05:00
19 changed files with 2679 additions and 181 deletions

100
Cargo.lock generated
View File

@@ -1754,6 +1754,24 @@ dependencies = [
"url", "url",
] ]
[[package]]
name = "example-ha-cluster"
version = "0.1.0"
dependencies = [
"brocade",
"cidr",
"env_logger",
"harmony",
"harmony_macros",
"harmony_secret",
"harmony_tui",
"harmony_types",
"log",
"serde",
"tokio",
"url",
]
[[package]] [[package]]
name = "example-kube-rs" name = "example-kube-rs"
version = "0.1.0" version = "0.1.0"
@@ -1942,9 +1960,28 @@ dependencies = [
"cidr", "cidr",
"env_logger", "env_logger",
"harmony", "harmony",
"harmony_cli",
"harmony_macros", "harmony_macros",
"harmony_secret", "harmony_secret",
"harmony_tui", "harmony_types",
"log",
"serde",
"tokio",
"url",
]
[[package]]
name = "example-opnsense-node-exporter"
version = "0.1.0"
dependencies = [
"async-trait",
"cidr",
"env_logger",
"harmony",
"harmony_cli",
"harmony_macros",
"harmony_secret",
"harmony_secret_derive",
"harmony_types", "harmony_types",
"log", "log",
"serde", "serde",
@@ -1982,25 +2019,6 @@ dependencies = [
"url", "url",
] ]
[[package]]
name = "example-opnsense-node-exporter"
version = "0.1.0"
dependencies = [
"async-trait",
"cidr",
"env_logger",
"harmony",
"harmony_cli",
"harmony_macros",
"harmony_secret",
"harmony_secret_derive",
"harmony_types",
"log",
"serde",
"tokio",
"url",
]
[[package]] [[package]]
name = "example-pxe" name = "example-pxe"
version = "0.1.0" version = "0.1.0"
@@ -3464,6 +3482,25 @@ dependencies = [
"thiserror 1.0.69", "thiserror 1.0.69",
] ]
[[package]]
name = "json-prompt"
version = "0.1.0"
dependencies = [
"brocade",
"cidr",
"env_logger",
"harmony",
"harmony_cli",
"harmony_macros",
"harmony_secret",
"harmony_secret_derive",
"harmony_types",
"log",
"serde",
"tokio",
"url",
]
[[package]] [[package]]
name = "jsonpath-rust" name = "jsonpath-rust"
version = "0.7.5" version = "0.7.5"
@@ -6062,6 +6099,25 @@ dependencies = [
"syn 2.0.106", "syn 2.0.106",
] ]
[[package]]
name = "sttest"
version = "0.1.0"
dependencies = [
"brocade",
"cidr",
"env_logger",
"harmony",
"harmony_cli",
"harmony_macros",
"harmony_secret",
"harmony_secret_derive",
"harmony_types",
"log",
"serde",
"tokio",
"url",
]
[[package]] [[package]]
name = "subtle" name = "subtle"
version = "2.6.1" version = "2.6.1"
@@ -7357,7 +7413,7 @@ checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
[[package]] [[package]]
name = "yaserde" name = "yaserde"
version = "0.12.0" version = "0.12.0"
source = "git+https://github.com/jggc/yaserde.git#adfdb1c5f4d054f114e5bd0ea7bda9c07a369def" source = "git+https://github.com/jggc/yaserde.git#2eacb304113beee7270a10b81046d40ed3a99550"
dependencies = [ dependencies = [
"log", "log",
"xml-rs", "xml-rs",
@@ -7366,7 +7422,7 @@ dependencies = [
[[package]] [[package]]
name = "yaserde_derive" name = "yaserde_derive"
version = "0.12.0" version = "0.12.0"
source = "git+https://github.com/jggc/yaserde.git#adfdb1c5f4d054f114e5bd0ea7bda9c07a369def" source = "git+https://github.com/jggc/yaserde.git#2eacb304113beee7270a10b81046d40ed3a99550"
dependencies = [ dependencies = [
"heck", "heck",
"log", "log",

View File

@@ -1,95 +0,0 @@
# Architecture Decision Record: Staleness-Based Failover Mechanism & Observability
**Status:** Proposed
**Date:** 2026-01-09
**Precedes:** [016-Harmony-Agent-And-Global-Mesh-For-Decentralized-Workload-Management.md](https://git.nationtech.io/NationTech/harmony/raw/branch/master/adr/016-Harmony-Agent-And-Global-Mesh-For-Decentralized-Workload-Management.md)
## Context
In ADR 016, we established the **Harmony Agent** and the **Global Orchestration Mesh** (powered by NATS JetStream) as the foundation for our decentralized infrastructure. We defined the high-level need for a `FailoverStrategy` that can support both financial consistency (CP) and AI availability (AP).
However, a specific implementation challenge remains: **How do we reliably detect node failure without losing the ability to debug the event later?**
Standard distributed systems often use "Key Expiration" (TTL) for heartbeats. If a key disappears, the node is presumed dead. While simple, this approach is catastrophic for post-mortem analysis. When the key expires, the evidence of *when* and *how* the failure occurred evaporates.
For NationTechs vision of **Humane Computing**—where micro datacenters might be heating a family home or running a local business—reliability and diagnosability are paramount. If a cluster fails over, we owe it to the user to provide a clear, historical log of exactly what happened. We cannot build a "wonderful future for computers" on ephemeral, untraceable errors.
## Decision
We will implement a **Staleness Detection** mechanism rather than a Key Expiration mechanism. We will leverage NATS JetStream Key-Value (KV) stores with **History Enabled** to create an immutable audit trail of cluster health.
### 1. The "Black Box" Flight Recorder (NATS Configuration)
We will utilize a persistent NATS KV bucket named `harmony_failover`.
* **Storage:** File (Persistent).
* **History:** Set to `64` (or higher). This allows us to query the last 64 heartbeat entries to visualize the exact degradation of the primary node before failure.
* **TTL:** None. Data never disappears; it only becomes "stale."
### 2. Data Structures
We will define two primary schemas to manage the state.
**A. The Rules of Engagement (`cluster_config`)**
This persistent key defines the behavior of the mesh. It allows us to tune failover sensitivity dynamically without redeploying the Agent binary.
```json
{
"primary_site_id": "site-a-basement",
"replica_site_id": "site-b-cloud",
"failover_timeout_ms": 5000, // Time before Replica takes over
"heartbeat_interval_ms": 1000 // Frequency of Primary updates
}
```
> **Note :** The location for this configuration data structure is TBD. See https://git.nationtech.io/NationTech/harmony/issues/206
**B. The Heartbeat (`primary_heartbeat`)**
The Primary writes this; the Replica watches it.
```json
{
"site_id": "site-a-basement",
"status": "HEALTHY",
"counter": 10452,
"timestamp": 1704661549000
}
```
### 3. The Failover Algorithm
**The Primary (Site A) Logic:**
The Primary's ability to write to the mesh is its "License to Operate."
1. **Write Loop:** Attempts to write `primary_heartbeat` every `heartbeat_interval_ms`.
2. **Self-Preservation (Fencing):** If the write fails (NATS Ack timeout or NATS unreachable), the Primary **immediately self-demotes**. It assumes it is network-isolated. This prevents Split Brain scenarios where a partitioned Primary continues to accept writes while the Replica promotes itself.
**The Replica (Site B) Logic:**
The Replica acts as the watchdog.
1. **Watch:** Subscribes to updates on `primary_heartbeat`.
2. **Staleness Check:** Maintains a local timer. Every time a heartbeat arrives, the timer resets.
3. **Promotion:** If the timer exceeds `failover_timeout_ms`, the Replica declares the Primary dead and promotes itself to Leader.
4. **Yielding:** If the Replica is Leader, but suddenly receives a valid, new heartbeat from the configured `primary_site_id` (indicating the Primary has recovered), the Replica will voluntarily **demote** itself to restore the preferred topology.
## Rationale
**Observability as a First-Class Citizen**
By keeping the last 64 heartbeats, we can run `nats kv history` to see the exact timeline. Did the Primary stop suddenly (crash)? or did the heartbeats become erratic and slow before stopping (network congestion)? This data is critical for optimizing the "Micro Data Centers" described in our vision, where internet connections in residential areas may vary in quality.
**Energy Efficiency & Resource Optimization**
NationTech aims to "maximize the value of our energy." A "flapping" cluster (constantly failing over and back) wastes immense energy in data re-synchronization and startup costs. By making the `failover_timeout_ms` configurable via `cluster_config`, we can tune a cluster heating a greenhouse to be less sensitive (slower failover is fine) compared to a cluster running a payment gateway.
**Decentralized Trust**
This architecture relies on NATS as the consensus engine. If the Primary is part of the NATS majority, it lives. If it isn't, it dies. This removes ambiguity and allows us to scale to thousands of independent sites without a central "God mode" controller managing every single failover.
## Consequences
**Positive**
* **Auditability:** Every failover event leaves a permanent trace in the KV history.
* **Safety:** The "Write Ack" check on the Primary provides a strong guarantee against Split Brain in `AbsoluteConsistency` mode.
* **Dynamic Tuning:** We can adjust timeouts for specific environments (e.g., high-latency satellite links) by updating a JSON key, requiring no downtime.
**Negative**
* **Storage Overhead:** Keeping history requires marginally more disk space on the NATS servers, though for 64 small JSON payloads, this is negligible.
* **Clock Skew:** While we rely on NATS server-side timestamps for ordering, extreme clock skew on the client side could confuse the debug logs (though not the failover logic itself).
## Alignment with Vision
This architecture supports the NationTech goal of a **"Beautifully Integrated Design."** It takes the complex, high-stakes problem of distributed consensus and wraps it in a mechanism that is robust enough for enterprise banking yet flexible enough to manage a basement server heating a swimming pool. It bridges the gap between the reliability of Web2 clouds and the decentralized nature of Web3 infrastructure.
```

View File

@@ -0,0 +1,22 @@
[package]
name = "sttest"
edition = "2024"
version.workspace = true
readme.workspace = true
license.workspace = true
publish = false
[dependencies]
harmony = { path = "../../harmony" }
harmony_cli = { path = "../../harmony_cli" }
harmony_types = { path = "../../harmony_types" }
cidr = { workspace = true }
tokio = { workspace = true }
harmony_macros = { path = "../../harmony_macros" }
harmony_secret = { path = "../../harmony_secret" }
harmony_secret_derive = { path = "../../harmony_secret_derive" }
log = { workspace = true }
env_logger = { workspace = true }
url = { workspace = true }
serde = { workspace = true }
brocade = { path = "../../brocade" }

1
examples/sttest/data Symbolic link
View File

@@ -0,0 +1 @@
../../data/

4
examples/sttest/env.sh Normal file
View File

@@ -0,0 +1,4 @@
export HARMONY_SECRET_NAMESPACE=sttest0
export HARMONY_SECRET_STORE=file
export HARMONY_DATABASE_URL=sqlite://harmony_sttest0.sqlite
export RUST_LOG=info

View File

@@ -0,0 +1,41 @@
mod topology;
use crate::topology::{get_inventory, get_topology};
use harmony::{
config::secret::SshKeyPair,
data::{FileContent, FilePath},
modules::{
inventory::HarmonyDiscoveryStrategy,
okd::{installation::OKDInstallationPipeline, ipxe::OKDIpxeScore},
},
score::Score,
topology::HAClusterTopology,
};
use harmony_secret::SecretManager;
#[tokio::main]
async fn main() {
// env_logger::init();
let inventory = get_inventory();
let topology = get_topology().await;
let ssh_key = SecretManager::get_or_prompt::<SshKeyPair>().await.unwrap();
let mut scores: Vec<Box<dyn Score<HAClusterTopology>>> = vec![Box::new(OKDIpxeScore {
kickstart_filename: "inventory.kickstart".to_string(),
harmony_inventory_agent: "harmony_inventory_agent".to_string(),
cluster_pubkey: FileContent {
path: FilePath::Relative("cluster_ssh_key.pub".to_string()),
content: ssh_key.public,
},
})];
// let mut scores: Vec<Box<dyn Score<HAClusterTopology>>> = vec![];
scores
.append(&mut OKDInstallationPipeline::get_all_scores(HarmonyDiscoveryStrategy::MDNS).await);
harmony_cli::run(inventory, topology, scores, None)
.await
.unwrap();
}

View File

@@ -0,0 +1,99 @@
use cidr::Ipv4Cidr;
use harmony::{
hardware::{Location, SwitchGroup},
infra::{brocade::UnmanagedSwitch, opnsense::OPNSenseManagementInterface},
inventory::Inventory,
topology::{HAClusterTopology, LogicalHost, UnmanagedRouter},
};
use harmony_macros::{ip, ipv4};
use harmony_secret::{Secret, SecretManager};
use serde::{Deserialize, Serialize};
use std::{
net::IpAddr,
sync::{Arc, OnceLock},
};
#[derive(Secret, Serialize, Deserialize, Debug, PartialEq)]
struct OPNSenseFirewallConfig {
username: String,
password: String,
}
pub async fn get_topology() -> HAClusterTopology {
let firewall = harmony::topology::LogicalHost {
ip: ip!("192.168.40.1"),
name: String::from("fw0"),
};
let switch_client = UnmanagedSwitch::init()
.await
.expect("Failed to connect to switch");
let switch_client = Arc::new(switch_client);
let config = SecretManager::get_or_prompt::<OPNSenseFirewallConfig>().await;
let config = config.unwrap();
let opnsense = Arc::new(
harmony::infra::opnsense::OPNSenseFirewall::new(
firewall,
None,
&config.username,
&config.password,
)
.await,
);
let lan_subnet = ipv4!("192.168.40.0");
let gateway_ipv4 = ipv4!("192.168.40.1");
let gateway_ip = IpAddr::V4(gateway_ipv4);
harmony::topology::HAClusterTopology {
kubeconfig: None,
domain_name: "sttest0.harmony.mcd".to_string(),
router: Arc::new(UnmanagedRouter::new(
gateway_ip,
Ipv4Cidr::new(lan_subnet, 24).unwrap(),
)),
load_balancer: opnsense.clone(),
firewall: opnsense.clone(),
tftp_server: opnsense.clone(),
http_server: opnsense.clone(),
dhcp_server: opnsense.clone(),
dns_server: opnsense.clone(),
control_plane: vec![
LogicalHost {
ip: ip!("192.168.40.20"),
name: "cp0".to_string(),
},
LogicalHost {
ip: ip!("192.168.40.21"),
name: "cp1".to_string(),
},
LogicalHost {
ip: ip!("192.168.40.22"),
name: "cp2".to_string(),
},
],
bootstrap_host: LogicalHost {
ip: ip!("192.168.40.10"),
name: "bootstrap".to_string(),
},
workers: vec![LogicalHost {
ip: ip!("192.168.40.30"),
name: "wk0".to_string(),
}],
node_exporter: opnsense.clone(),
switch_client: switch_client.clone(),
network_manager: OnceLock::new(),
}
}
pub fn get_inventory() -> Inventory {
Inventory {
location: Location::new("Sylvain's basement".to_string(), "Charlesbourg".to_string()),
switch: SwitchGroup::from([]),
firewall_mgmt: Box::new(OPNSenseManagementInterface::new()),
storage_host: vec![],
worker_host: vec![],
control_plane_host: vec![],
}
}

View File

@@ -22,7 +22,7 @@ pub struct OKDSetup04WorkersScore {
impl Score<HAClusterTopology> for OKDSetup04WorkersScore { impl Score<HAClusterTopology> for OKDSetup04WorkersScore {
fn create_interpret(&self) -> Box<dyn Interpret<HAClusterTopology>> { fn create_interpret(&self) -> Box<dyn Interpret<HAClusterTopology>> {
Box::new(OKDNodeInterpret::new( Box::new(OKDNodeInterpret::new(
HostRole::ControlPlane, HostRole::Worker,
self.discovery_strategy.clone(), self.discovery_strategy.clone(),
)) ))
} }

View File

@@ -9,6 +9,7 @@ license.workspace = true
serde = { version = "1.0.123", features = [ "derive" ] } serde = { version = "1.0.123", features = [ "derive" ] }
log = { workspace = true } log = { workspace = true }
env_logger = { workspace = true } env_logger = { workspace = true }
#yaserde = { path = "../../yaserde/yaserde" }
yaserde = { git = "https://github.com/jggc/yaserde.git" } yaserde = { git = "https://github.com/jggc/yaserde.git" }
yaserde_derive = { git = "https://github.com/jggc/yaserde.git" } yaserde_derive = { git = "https://github.com/jggc/yaserde.git" }
xml-rs = "0.8" xml-rs = "0.8"

View File

@@ -8,6 +8,8 @@ pub struct Pischem {
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct Caddy { pub struct Caddy {
#[yaserde(attribute = true)]
pub version: Option<String>,
pub general: CaddyGeneral, pub general: CaddyGeneral,
pub reverseproxy: MaybeString, pub reverseproxy: MaybeString,
} }

View File

@@ -8,6 +8,8 @@ pub struct DnsMasq {
pub version: String, pub version: String,
#[yaserde(attribute = true)] #[yaserde(attribute = true)]
pub persisted_at: Option<String>, pub persisted_at: Option<String>,
#[yaserde(attribute = true)]
pub description: Option<String>,
pub enable: u8, pub enable: u8,
pub regdhcp: u8, pub regdhcp: u8,
@@ -23,7 +25,7 @@ pub struct DnsMasq {
pub dnssec: u8, pub dnssec: u8,
pub regdhcpdomain: MaybeString, pub regdhcpdomain: MaybeString,
pub interface: Option<String>, pub interface: Option<String>,
pub port: Option<u32>, pub port: Option<MaybeString>,
pub dns_forward_max: MaybeString, pub dns_forward_max: MaybeString,
pub cache_size: MaybeString, pub cache_size: MaybeString,
pub local_ttl: MaybeString, pub local_ttl: MaybeString,
@@ -73,6 +75,8 @@ pub struct Dhcp {
pub reply_delay: MaybeString, pub reply_delay: MaybeString,
pub enable_ra: u8, pub enable_ra: u8,
pub nosync: u8, pub nosync: u8,
pub log_dhcp: Option<u8>,
pub log_quiet: Option<u8>,
} }
// Represents a single <dhcp_ranges> element. // Represents a single <dhcp_ranges> element.

View File

@@ -598,7 +598,7 @@ 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,
pub weight: Option<u32>, pub weight: Option<MaybeString>,
#[yaserde(rename = "checkInterval")] #[yaserde(rename = "checkInterval")]
pub check_interval: MaybeString, pub check_interval: MaybeString,
#[yaserde(rename = "checkDownInterval")] #[yaserde(rename = "checkDownInterval")]

View File

@@ -30,6 +30,7 @@ pub struct OPNsense {
pub staticroutes: StaticRoutes, pub staticroutes: StaticRoutes,
pub ca: MaybeString, pub ca: MaybeString,
pub gateways: Option<RawXml>, pub gateways: Option<RawXml>,
pub hostwatch: Option<RawXml>,
pub cert: Vec<Cert>, pub cert: Vec<Cert>,
pub dhcpdv6: DhcpDv6, pub dhcpdv6: DhcpDv6,
pub virtualip: VirtualIp, pub virtualip: VirtualIp,
@@ -162,11 +163,15 @@ pub struct Username {
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct Sysctl { pub struct Sysctl {
#[yaserde(attribute = true)]
pub version: Option<String>,
pub item: Vec<SysctlItem>, pub item: Vec<SysctlItem>,
} }
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct SysctlItem { pub struct SysctlItem {
#[yaserde(attribute = true)]
pub uuid: Option<String>,
pub descr: Option<MaybeString>, pub descr: Option<MaybeString>,
pub tunable: Option<String>, pub tunable: Option<String>,
pub value: Option<MaybeString>, pub value: Option<MaybeString>,
@@ -174,6 +179,8 @@ pub struct SysctlItem {
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct System { pub struct System {
#[yaserde(attribute = true)]
pub uuid: Option<String>,
pub use_mfs_tmp: Option<MaybeString>, pub use_mfs_tmp: Option<MaybeString>,
pub use_mfs_var: Option<MaybeString>, pub use_mfs_var: Option<MaybeString>,
pub serialspeed: u32, pub serialspeed: u32,
@@ -268,6 +275,8 @@ pub struct Bogons {
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct Group { pub struct Group {
#[yaserde(attribute = true)]
pub uuid: Option<String>,
pub name: String, pub name: String,
pub description: Option<String>, pub description: Option<String>,
pub scope: String, pub scope: String,
@@ -280,6 +289,8 @@ pub struct Group {
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct User { pub struct User {
#[yaserde(attribute = true)]
pub uuid: Option<String>,
pub name: String, pub name: String,
pub descr: MaybeString, pub descr: MaybeString,
pub scope: String, pub scope: String,
@@ -463,6 +474,8 @@ pub struct OPNsenseXmlSection {
pub openvpn: ConfigOpenVPN, pub openvpn: ConfigOpenVPN,
#[yaserde(rename = "Gateways")] #[yaserde(rename = "Gateways")]
pub gateways: RawXml, pub gateways: RawXml,
#[yaserde(rename = "Hostwatch")]
pub hostwatch: Option<RawXml>,
#[yaserde(rename = "HAProxy")] #[yaserde(rename = "HAProxy")]
pub haproxy: Option<HAProxy>, pub haproxy: Option<HAProxy>,
} }
@@ -1143,9 +1156,9 @@ pub struct UnboundGeneral {
pub dns64: MaybeString, pub dns64: MaybeString,
pub dns64prefix: MaybeString, pub dns64prefix: MaybeString,
pub noarecords: MaybeString, pub noarecords: MaybeString,
pub regdhcp: Option<i8>, pub regdhcp: Option<MaybeString>,
pub regdhcpdomain: MaybeString, pub regdhcpdomain: MaybeString,
pub regdhcpstatic: Option<i8>, pub regdhcpstatic: Option<MaybeString>,
pub noreglladdr6: MaybeString, pub noreglladdr6: MaybeString,
pub noregrecords: MaybeString, pub noregrecords: MaybeString,
pub txtsupport: MaybeString, pub txtsupport: MaybeString,
@@ -1153,27 +1166,27 @@ pub struct UnboundGeneral {
pub local_zone_type: String, pub local_zone_type: String,
pub outgoing_interface: MaybeString, pub outgoing_interface: MaybeString,
pub enable_wpad: MaybeString, pub enable_wpad: MaybeString,
pub safesearch: MaybeString, pub safesearch: Option<MaybeString>,
} }
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct Advanced { pub struct Advanced {
pub hideidentity: Option<i8>, pub hideidentity: Option<MaybeString>,
pub hideversion: Option<i8>, pub hideversion: Option<MaybeString>,
pub prefetch: Option<i8>, pub prefetch: Option<MaybeString>,
pub prefetchkey: Option<i8>, pub prefetchkey: Option<MaybeString>,
pub dnssecstripped: Option<i8>, pub dnssecstripped: Option<MaybeString>,
pub aggressivensec: Option<i8>, pub aggressivensec: Option<i8>,
pub serveexpired: Option<i8>, pub serveexpired: Option<MaybeString>,
pub serveexpiredreplyttl: MaybeString, pub serveexpiredreplyttl: MaybeString,
pub serveexpiredttl: MaybeString, pub serveexpiredttl: MaybeString,
pub serveexpiredttlreset: Option<i32>, pub serveexpiredttlreset: Option<MaybeString>,
pub serveexpiredclienttimeout: MaybeString, pub serveexpiredclienttimeout: MaybeString,
pub qnameminstrict: Option<i32>, pub qnameminstrict: Option<MaybeString>,
pub extendedstatistics: Option<i32>, pub extendedstatistics: Option<MaybeString>,
pub logqueries: Option<i32>, pub logqueries: Option<MaybeString>,
pub logreplies: Option<i32>, pub logreplies: Option<MaybeString>,
pub logtagqueryreply: Option<i32>, pub logtagqueryreply: Option<MaybeString>,
pub logservfail: MaybeString, pub logservfail: MaybeString,
pub loglocalactions: MaybeString, pub loglocalactions: MaybeString,
pub logverbosity: i32, pub logverbosity: i32,
@@ -1216,12 +1229,12 @@ pub struct Dnsbl {
pub blocklists: Option<MaybeString>, pub blocklists: Option<MaybeString>,
pub wildcards: Option<MaybeString>, pub wildcards: Option<MaybeString>,
pub address: Option<MaybeString>, pub address: Option<MaybeString>,
pub nxdomain: Option<i32>, pub nxdomain: Option<MaybeString>,
} }
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct Forwarding { pub struct Forwarding {
pub enabled: Option<i32>, pub enabled: Option<MaybeString>,
} }
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
@@ -1243,7 +1256,7 @@ pub struct Host {
pub ttl: Option<MaybeString>, pub ttl: Option<MaybeString>,
pub server: String, pub server: String,
pub description: Option<String>, pub description: Option<String>,
pub txtdata: MaybeString, pub txtdata: Option<MaybeString>,
} }
impl Host { impl Host {
@@ -1259,7 +1272,7 @@ impl Host {
ttl: Some(MaybeString::default()), ttl: Some(MaybeString::default()),
mx: MaybeString::default(), mx: MaybeString::default(),
description: None, description: None,
txtdata: MaybeString::default(), txtdata: Some(MaybeString::default()),
} }
} }
} }
@@ -1421,7 +1434,7 @@ pub struct StaticRoutes {
#[yaserde(attribute = true)] #[yaserde(attribute = true)]
pub version: String, pub version: String,
#[yaserde(rename = "route")] #[yaserde(rename = "route")]
pub route: Option<MaybeString>, pub route: Option<RawXml>,
} }
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]

View File

@@ -234,14 +234,15 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn test_load_config_from_local_file() { async fn test_load_config_from_local_file() {
for path in [ for path in [
// "src/tests/data/config-opnsense-25.1.xml", "src/tests/data/config-opnsense-25.1.xml",
// "src/tests/data/config-vm-test.xml", "src/tests/data/config-vm-test.xml",
"src/tests/data/config-structure.xml", "src/tests/data/config-structure.xml",
"src/tests/data/config-full-1.xml", "src/tests/data/config-full-1.xml",
// "src/tests/data/config-full-ncd0.xml", // "src/tests/data/config-full-ncd0.xml",
// "src/tests/data/config-full-25.7.xml", // "src/tests/data/config-full-25.7.xml",
// "src/tests/data/config-full-25.7-dummy-dnsmasq-options.xml", // "src/tests/data/config-full-25.7-dummy-dnsmasq-options.xml",
"src/tests/data/config-25.7-dnsmasq-static-host.xml", "src/tests/data/config-25.7-dnsmasq-static-host.xml",
"src/tests/data/config-full-25.7.11_2.xml",
] { ] {
let mut test_file_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let mut test_file_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
test_file_path.push(path); test_file_path.push(path);

View File

@@ -1,4 +1,4 @@
use opnsense_config_xml::{Host, OPNsense}; use opnsense_config_xml::{Host, MaybeString, OPNsense};
pub struct UnboundDnsConfig<'a> { pub struct UnboundDnsConfig<'a> {
opnsense: &'a mut OPNsense, opnsense: &'a mut OPNsense,
@@ -31,7 +31,8 @@ impl<'a> UnboundDnsConfig<'a> {
None => todo!("Handle case where unboundplus is not used"), None => todo!("Handle case where unboundplus is not used"),
}; };
unbound.general.regdhcp = Some(register as i8); unbound.general.regdhcp = Some(MaybeString::from_bool_as_int("regdhcp", register));
unbound.general.regdhcpstatic = Some(register as i8); unbound.general.regdhcpstatic =
Some(MaybeString::from_bool_as_int("regdhcpstatic", register));
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -271,7 +271,6 @@
</firmware> </firmware>
<language>en_US</language> <language>en_US</language>
<dnsserver>1.1.1.1</dnsserver> <dnsserver>1.1.1.1</dnsserver>
<dnsserver>8.8.8.8</dnsserver>
<dns1gw>none</dns1gw> <dns1gw>none</dns1gw>
<dns2gw>none</dns2gw> <dns2gw>none</dns2gw>
<dns3gw>none</dns3gw> <dns3gw>none</dns3gw>

View File

@@ -30,28 +30,17 @@
<item uuid="b6b18051-830f-4b27-81ec-f772b14681e2"> <item uuid="b6b18051-830f-4b27-81ec-f772b14681e2">
<tunable>net.inet.ip.sourceroute</tunable> <tunable>net.inet.ip.sourceroute</tunable>
<value>default</value> <value>default</value>
<descr> <descr>Source routing is another way for an attacker to try to reach non-routable addresses behind your box. It can also be used to probe for information about your internal networks. These functions come enabled as part of the standard FreeBSD core system.</descr>
Source routing is another way for an attacker to try to reach non-routable addresses behind your box.
It can also be used to probe for information about your internal networks. These functions come enabled
as part of the standard FreeBSD core system.
</descr>
</item> </item>
<item uuid="ea21409c-62d6-4040-aa2b-36bd01af5578"> <item uuid="ea21409c-62d6-4040-aa2b-36bd01af5578">
<tunable>net.inet.ip.accept_sourceroute</tunable> <tunable>net.inet.ip.accept_sourceroute</tunable>
<value>default</value> <value>default</value>
<descr> <descr>Source routing is another way for an attacker to try to reach non-routable addresses behind your box. It can also be used to probe for information about your internal networks. These functions come enabled as part of the standard FreeBSD core system.</descr>
Source routing is another way for an attacker to try to reach non-routable addresses behind your box.
It can also be used to probe for information about your internal networks. These functions come enabled
as part of the standard FreeBSD core system.
</descr>
</item> </item>
<item uuid="1613256c-ef7e-4b53-a44c-234440046293"> <item uuid="1613256c-ef7e-4b53-a44c-234440046293">
<tunable>net.inet.icmp.log_redirect</tunable> <tunable>net.inet.icmp.log_redirect</tunable>
<value>default</value> <value>default</value>
<descr> <descr>This option turns off the logging of redirect packets because there is no limit and this could fill up your logs consuming your whole hard drive.</descr>
This option turns off the logging of redirect packets because there is no limit and this could fill
up your logs consuming your whole hard drive.
</descr>
</item> </item>
<item uuid="1ba88c72-6e5b-4f19-abba-351c2b76d5dc"> <item uuid="1ba88c72-6e5b-4f19-abba-351c2b76d5dc">
<tunable>net.inet.tcp.drop_synfin</tunable> <tunable>net.inet.tcp.drop_synfin</tunable>
@@ -181,9 +170,7 @@
<item uuid="2c42ae2f-a7bc-48cb-b27d-db72e738e80b"> <item uuid="2c42ae2f-a7bc-48cb-b27d-db72e738e80b">
<tunable>net.inet.ip.redirect</tunable> <tunable>net.inet.ip.redirect</tunable>
<value>default</value> <value>default</value>
<descr>Enable/disable sending of ICMP redirects in response to IP packets for which a better, <descr>Enable/disable sending of ICMP redirects in response to IP packets for which a better, and for the sender directly reachable, route and next hop is known.</descr>
and for the sender directly reachable, route and next hop is known.
</descr>
</item> </item>
<item uuid="7d315fb1-c638-4b79-9f6c-240b41e6d643"> <item uuid="7d315fb1-c638-4b79-9f6c-240b41e6d643">
<tunable>net.local.dgram.maxdgram</tunable> <tunable>net.local.dgram.maxdgram</tunable>
@@ -938,4 +925,3 @@
</cert> </cert>
<syslog/> <syslog/>
</opnsense> </opnsense>

View File

@@ -28,28 +28,17 @@
<value>default</value> <value>default</value>
</item> </item>
<item> <item>
<descr> <descr>Source routing is another way for an attacker to try to reach non-routable addresses behind your box. It can also be used to probe for information about your internal networks. These functions come enabled as part of the standard FreeBSD core system.</descr>
Source routing is another way for an attacker to try to reach non-routable addresses behind your box.
It can also be used to probe for information about your internal networks. These functions come enabled
as part of the standard FreeBSD core system.
</descr>
<tunable>net.inet.ip.sourceroute</tunable> <tunable>net.inet.ip.sourceroute</tunable>
<value>default</value> <value>default</value>
</item> </item>
<item> <item>
<descr> <descr>Source routing is another way for an attacker to try to reach non-routable addresses behind your box. It can also be used to probe for information about your internal networks. These functions come enabled as part of the standard FreeBSD core system.</descr>
Source routing is another way for an attacker to try to reach non-routable addresses behind your box.
It can also be used to probe for information about your internal networks. These functions come enabled
as part of the standard FreeBSD core system.
</descr>
<tunable>net.inet.ip.accept_sourceroute</tunable> <tunable>net.inet.ip.accept_sourceroute</tunable>
<value>default</value> <value>default</value>
</item> </item>
<item> <item>
<descr> <descr>This option turns off the logging of redirect packets because there is no limit and this could fill up your logs consuming your whole hard drive.</descr>
This option turns off the logging of redirect packets because there is no limit and this could fill
up your logs consuming your whole hard drive.
</descr>
<tunable>net.inet.icmp.log_redirect</tunable> <tunable>net.inet.icmp.log_redirect</tunable>
<value>default</value> <value>default</value>
</item> </item>
@@ -179,9 +168,7 @@
<value>default</value> <value>default</value>
</item> </item>
<item> <item>
<descr>Enable/disable sending of ICMP redirects in response to IP packets for which a better, <descr>Enable/disable sending of ICMP redirects in response to IP packets for which a better, and for the sender directly reachable, route and next hop is known.</descr>
and for the sender directly reachable, route and next hop is known.
</descr>
<tunable>net.inet.ip.redirect</tunable> <tunable>net.inet.ip.redirect</tunable>
<value>default</value> <value>default</value>
</item> </item>