aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGus Power <gus@infinitesidequests.com>2025-06-07 17:33:19 +0100
committerGus Power <gus@infinitesidequests.com>2025-06-07 17:33:19 +0100
commit613bac3ac614a7e2ba965d59a078a930ec5a1e84 (patch)
tree4e383b2b4ccb49542345317586e88338dac590e5
parent172f8163139f8112b76d462198a1213a5cb49dde (diff)
verify specified network adapters actually existmain
-rw-r--r--src/config.rs39
-rw-r--r--src/error.rs12
-rw-r--r--src/lib.rs1
-rw-r--r--src/main.rs3
-rw-r--r--src/network.rs2
-rw-r--r--src/test_macros.rs11
6 files changed, 58 insertions, 10 deletions
diff --git a/src/config.rs b/src/config.rs
index d9dbe74..181f099 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -1,6 +1,7 @@
use crate::dyndns_service::DynDnsProvider;
use crate::error::{AppError, AppResult};
use crate::ip_service::IpServiceProvider;
+use crate::network::get_active_network_adapters;
use fqdn::FQDN;
use log::warn;
use serde::{Deserialize, Serialize};
@@ -50,6 +51,17 @@ impl Config {
Err(AppError::ConfigFileNotProvided)
}
+
+ pub fn validate(self) -> AppResult<Self> {
+ let interfaces: Vec<String> = self.networks.iter().filter_map(|network| network.interface.clone()).collect();
+ if !interfaces.is_empty() {
+ let adapters = get_active_network_adapters()?;
+ adapters.iter().find(|adapter| interfaces.contains(&adapter.name))
+ .ok_or(AppError::UnableToFindNetworkInterface(interfaces.join(",")))?;
+ }
+
+ Ok(self)
+ }
}
#[derive(Debug, Deserialize, Serialize)]
@@ -80,6 +92,12 @@ pub struct DnsRecord {
record_type: DnsRecordType,
}
+impl DnsRecord {
+ pub fn new(fqdn: FQDN) -> Self {
+ Self { fqdn, ttl: default_ttl(), record_type: default_record_type() }
+ }
+}
+
fn default_record_type() -> DnsRecordType {
DnsRecordType::A
}
@@ -92,10 +110,7 @@ mod tests {
use super::*;
use crate::dyndns_service::DynDnsProvider::Gandi;
use crate::dyndns_service::gandi::GandiConfig;
- use crate::{
- assert_config_file_not_found, assert_config_parse_error, assert_error, assert_io_error,
- test,
- };
+ use crate::{assert_config_file_not_found, assert_config_parse_error, assert_error, assert_io_error, assert_unable_to_find_network_interface_error, test};
use serde_json::json;
use std::fs::read_dir;
use std::path::{Path, PathBuf};
@@ -190,4 +205,20 @@ mod tests {
assert_io_error!(Config::load(vec![path.to_path_buf()]));
}
}
+
+ test! {
+ fn unknown_network_interface() {
+ let config = Config {
+ networks: vec![WanConfig {
+ interface: Some("unknown0".to_string()),
+ dns_record: DnsRecord::new(FQDN::from_str("some.domain.com")?),
+ providers: vec![DynDnsProvider::Noop],
+ ip_service: IpServiceProvider::Noop,
+ }]
+ };
+
+ assert_unable_to_find_network_interface_error!(config.validate(), "unknown0");
+ }
+ }
+
}
diff --git a/src/error.rs b/src/error.rs
index 1b71511..7d1700f 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -17,7 +17,8 @@ pub enum AppError {
InvalidResponse { url: Url, reason: String },
InvalidHttpHeader(String),
UnableToGetHomeDirectory(GetHomeError),
- NetworkInterfaceError(NetworkInterfaceError),
+ UnableToListNetworkInterfaces(NetworkInterfaceError),
+ UnableToFindNetworkInterface(String),
}
impl fmt::Display for AppError {
@@ -43,8 +44,11 @@ impl fmt::Display for AppError {
Self::UnableToGetHomeDirectory(err) => {
write!(f, "Failed to get home directory: {}", err)
}
- Self::NetworkInterfaceError(err) => {
- write!(f, "Network interface error: {}", err)
+ Self::UnableToListNetworkInterfaces(err) => {
+ write!(f, "Unable to list network interfaces: {}", err)
+ }
+ Self::UnableToFindNetworkInterface(err) => {
+ write!(f, "Unable to find network interface: {}", err)
}
}
}
@@ -56,7 +60,7 @@ impl std::error::Error for AppError {
Self::IoError(err) => Some(err),
Self::ConfigParseError { source, .. } => Some(source),
Self::RequestFailed { source, .. } => Some(source),
- Self::NetworkInterfaceError(err) => Some(err),
+ Self::UnableToListNetworkInterfaces(err) => Some(err),
_ => None,
}
}
diff --git a/src/lib.rs b/src/lib.rs
index 083cf46..580d5a6 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -3,6 +3,7 @@ mod dyndns_service;
mod error;
mod http;
mod ip_service;
+mod network;
#[cfg(test)]
#[macro_use]
diff --git a/src/main.rs b/src/main.rs
index 3db2582..e46b22d 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -7,6 +7,7 @@ use log::info;
use reqwest::ClientBuilder;
use std::path::PathBuf;
use std::process::exit;
+use crate::network::get_active_network_adapters;
mod config;
mod dyndns_service;
@@ -76,7 +77,7 @@ async fn main() {
}
async fn run(args: Args) -> AppResult<()> {
- let config = Config::load(args.get_config_files())?;
+ let config = Config::load(args.get_config_files())?.validate()?;
let client = ClientBuilder::new().build()?;
for network in config.networks {
let ip = network.ip_service.resolve(&client).await?;
diff --git a/src/network.rs b/src/network.rs
index 3697e29..cd717a1 100644
--- a/src/network.rs
+++ b/src/network.rs
@@ -30,7 +30,7 @@ impl From<NetworkInterface> for NetworkAdapter {
pub fn get_network_adapters() -> AppResult<Vec<NetworkAdapter>> {
NetworkInterface::show()
- .map_err(AppError::NetworkInterfaceError)
+ .map_err(AppError::UnableToListNetworkInterfaces)
.map(|interfaces| interfaces.into_iter().map(NetworkAdapter::from).collect())
}
diff --git a/src/test_macros.rs b/src/test_macros.rs
index 7887647..9eb1467 100644
--- a/src/test_macros.rs
+++ b/src/test_macros.rs
@@ -50,6 +50,17 @@ macro_rules! assert_io_error {
}
#[macro_export]
+macro_rules! assert_unable_to_find_network_interface_error {
+ ($result:expr, $expected_adapter:expr) => {
+ assert_error!($result, AppError::UnableToFindNetworkInterface(adapter) => {
+ assert_eq!(adapter, $expected_adapter,
+ "Expected network adapter {:?}, but got {:?}",
+ $expected_adapter, adapter);
+ });
+ };
+}
+
+#[macro_export]
macro_rules! assert_config_parse_error {
($result:expr, $expected_path:expr) => {
assert_error!($result, AppError::ConfigParseError { path, source: _ } => {