167 lines
5.7 KiB
Rust
167 lines
5.7 KiB
Rust
use std::{net::Ipv4Addr, sync::Arc, time::Duration};
|
|
|
|
use crate::{config::{SshConfigManager, SshCredentials, SshOPNSenseShell}, error::Error, modules::{dhcp::DhcpConfig, dns::DnsConfig}};
|
|
use log::trace;
|
|
use opnsense_config_xml::OPNsense;
|
|
use russh::client;
|
|
|
|
use super::{ConfigManager, OPNsenseShell};
|
|
|
|
#[derive(Debug)]
|
|
pub struct Config {
|
|
opnsense: OPNsense,
|
|
repository: Arc<dyn ConfigManager>,
|
|
shell: Arc<dyn OPNsenseShell>,
|
|
}
|
|
|
|
impl Config {
|
|
pub async fn new(
|
|
repository: Arc<dyn ConfigManager>,
|
|
shell: Arc<dyn OPNsenseShell>,
|
|
) -> Result<Self, Error> {
|
|
let xml = repository.load_as_str().await?;
|
|
trace!("xml {}", xml);
|
|
|
|
let opnsense = OPNsense::from(xml);
|
|
|
|
Ok(Self {
|
|
opnsense,
|
|
repository,
|
|
shell,
|
|
})
|
|
}
|
|
|
|
pub fn dhcp(&mut self) -> DhcpConfig {
|
|
DhcpConfig::new(&mut self.opnsense, self.shell.clone())
|
|
}
|
|
|
|
pub fn dns(&mut self) -> DnsConfig {
|
|
DnsConfig::new(&mut self.opnsense, self.shell.clone())
|
|
}
|
|
|
|
pub async fn restart_dns(&self) -> Result<(), Error> {
|
|
self.shell.exec("configctl unbound restart").await?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Save the config to the repository. This method is meant NOT to reload services, only save
|
|
/// the config to the live file/database and perhaps take a backup when relevant.
|
|
pub async fn save(&self) -> Result<(), Error> {
|
|
self.repository
|
|
.save_config(&self.opnsense.to_xml())
|
|
.await
|
|
}
|
|
|
|
/// Save the configuration and reload all services. Be careful with this one as it will cause
|
|
/// downtime in many cases, such as a PPPoE renegociation
|
|
pub async fn apply(&self) -> Result<(), Error> {
|
|
self.repository
|
|
.apply_new_config(&self.opnsense.to_xml())
|
|
.await
|
|
}
|
|
|
|
pub async fn from_credentials(ipaddr: std::net::IpAddr, username: &str, password: &str) -> Self {
|
|
let config = Arc::new(client::Config {
|
|
inactivity_timeout: Some(Duration::from_secs(5)),
|
|
..<_>::default()
|
|
});
|
|
|
|
let credentials = SshCredentials::Password {
|
|
username: String::from(username),
|
|
password: String::from(password),
|
|
};
|
|
|
|
let shell = Arc::new(SshOPNSenseShell::new(
|
|
(ipaddr, 22),
|
|
credentials,
|
|
config,
|
|
));
|
|
let manager = Arc::new(SshConfigManager::new(shell.clone()));
|
|
|
|
Config::new(manager, shell).await.unwrap()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate::config::{DummyOPNSenseShell, LocalFileConfigManager};
|
|
use crate::modules::dhcp::DhcpConfig;
|
|
use std::fs;
|
|
use std::net::Ipv4Addr;
|
|
|
|
use super::*;
|
|
use pretty_assertions::assert_eq;
|
|
use std::path::PathBuf;
|
|
|
|
#[tokio::test]
|
|
async fn test_load_config_from_local_file() {
|
|
for path in vec![
|
|
"src/tests/data/config-vm-test.xml",
|
|
"src/tests/data/config-full-1.xml",
|
|
"src/tests/data/config-structure.xml",
|
|
] {
|
|
let mut test_file_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
|
test_file_path.push(path);
|
|
|
|
let config_file_path = test_file_path.to_str().unwrap().to_string();
|
|
println!("File path {config_file_path}");
|
|
let repository = Arc::new(LocalFileConfigManager::new(config_file_path));
|
|
let shell = Arc::new(DummyOPNSenseShell {});
|
|
let config_file_str = repository.load_as_str().await.unwrap();
|
|
let config = Config::new(repository, shell)
|
|
.await
|
|
.expect("Failed to load config");
|
|
|
|
println!("Config {:?}", config);
|
|
|
|
let serialized = config.opnsense.to_xml();
|
|
|
|
fs::write("/tmp/serialized.xml", &serialized).unwrap();
|
|
|
|
// Since the order of all fields is not always the same in opnsense config files
|
|
// I think it is good enough to have exactly the same amount of the same lines
|
|
let config_file_str_sorted = vec![config_file_str.lines().collect::<Vec<_>>()].sort();
|
|
let serialized_sorted = vec![config_file_str.lines().collect::<Vec<_>>()].sort();
|
|
assert_eq!(config_file_str_sorted, serialized_sorted);
|
|
}
|
|
}
|
|
|
|
#[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-structure.xml");
|
|
|
|
let config_file_path = test_file_path.to_str().unwrap().to_string();
|
|
println!("File path {config_file_path}");
|
|
let repository = Arc::new(LocalFileConfigManager::new(config_file_path));
|
|
let shell = Arc::new(DummyOPNSenseShell {});
|
|
let mut config = Config::new(repository, shell.clone())
|
|
.await
|
|
.expect("Failed to load config");
|
|
|
|
println!("Config {:?}", config);
|
|
|
|
let mut dhcp_config = DhcpConfig::new(&mut config.opnsense, shell);
|
|
dhcp_config
|
|
.add_static_mapping(
|
|
"00:00:00:00:00:00",
|
|
Ipv4Addr::new(192, 168, 20, 100),
|
|
"hostname",
|
|
)
|
|
.expect("Should add static mapping");
|
|
|
|
let serialized = config.opnsense.to_xml();
|
|
|
|
fs::write("/tmp/serialized.xml", &serialized).unwrap();
|
|
|
|
let mut test_file_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
|
test_file_path.push("src/tests/data/config-structure-with-dhcp-staticmap-entry.xml");
|
|
|
|
let config_file_path = test_file_path.to_str().unwrap().to_string();
|
|
println!("File path {config_file_path}");
|
|
let repository = Box::new(LocalFileConfigManager::new(config_file_path));
|
|
let expected_config_file_str = repository.load_as_str().await.unwrap();
|
|
assert_eq!(expected_config_file_str, serialized);
|
|
}
|
|
}
|