aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock130
-rw-r--r--Cargo.toml15
-rw-r--r--src/error.rs12
-rw-r--r--src/http.rs46
-rw-r--r--src/lib.rs11
-rw-r--r--src/main.rs3
-rw-r--r--src/network.rs93
7 files changed, 262 insertions, 48 deletions
diff --git a/Cargo.lock b/Cargo.lock
index ebb67ab..65c765d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -82,12 +82,12 @@ dependencies = [
[[package]]
name = "anstyle-wincon"
-version = "3.0.7"
+version = "3.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e"
+checksum = "6680de5231bd6ee4c6191b8a1325daa282b415391ec9d3a37bd34f2060dc73fa"
dependencies = [
"anstyle",
- "once_cell",
+ "once_cell_polyfill",
"windows-sys 0.59.0",
]
@@ -165,9 +165,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
[[package]]
name = "cc"
-version = "1.2.23"
+version = "1.2.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f4ac86a9e5bc1e2b3449ab9d7d3a6a405e3d1bb28d7b9be8614f55846ae3766"
+checksum = "16595d3be041c03b09d08d0858631facccee9221e579704070e6e9e4915d3bc7"
dependencies = [
"shlex",
]
@@ -674,11 +674,10 @@ dependencies = [
[[package]]
name = "hyper-rustls"
-version = "0.27.5"
+version = "0.27.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2"
+checksum = "03a01595e11bdcec50946522c32dde3fc6914743000a68b93000965f2f02406d"
dependencies = [
- "futures-util",
"http",
"hyper",
"hyper-util",
@@ -707,9 +706,9 @@ dependencies = [
[[package]]
name = "hyper-util"
-version = "0.1.11"
+version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2"
+checksum = "cf9f1e950e0d9d1d3c47184416723cf29c0d1f93bd8cccf37e4beb6b44f31710"
dependencies = [
"bytes",
"futures-channel",
@@ -737,7 +736,7 @@ dependencies = [
"js-sys",
"log",
"wasm-bindgen",
- "windows-core 0.61.1",
+ "windows-core 0.61.2",
]
[[package]]
@@ -798,9 +797,9 @@ checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3"
[[package]]
name = "icu_properties"
-version = "2.0.0"
+version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2549ca8c7241c82f59c80ba2a6f415d931c5b58d24fb8412caa1a1f02c49139a"
+checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b"
dependencies = [
"displaydoc",
"icu_collections",
@@ -814,9 +813,9 @@ dependencies = [
[[package]]
name = "icu_properties_data"
-version = "2.0.0"
+version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8197e866e47b68f8f7d95249e172903bec06004b18b2937f1095d40a0c57de04"
+checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632"
[[package]]
name = "icu_provider"
@@ -904,9 +903,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "jiff"
-version = "0.2.13"
+version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f02000660d30638906021176af16b17498bd0d12813dbfe7b276d8bc7f3c0806"
+checksum = "a194df1107f33c79f4f93d02c80798520551949d59dfad22b6157048a88cca93"
dependencies = [
"jiff-static",
"log",
@@ -917,9 +916,9 @@ dependencies = [
[[package]]
name = "jiff-static"
-version = "0.2.13"
+version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f3c30758ddd7188629c6713fc45d1188af4f44c90582311d0c8d8c9907f60c48"
+checksum = "6c6e1db7ed32c6c71b759497fae34bf7933636f75a251b9e736555da426f6442"
dependencies = [
"proc-macro2",
"quote",
@@ -983,13 +982,13 @@ dependencies = [
[[package]]
name = "mio"
-version = "1.0.3"
+version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
+checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
dependencies = [
"libc",
"wasi 0.11.0+wasi-snapshot-preview1",
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
]
[[package]]
@@ -1002,6 +1001,7 @@ dependencies = [
"homedir",
"http",
"log",
+ "network-interface",
"reqwest",
"serde",
"serde_json",
@@ -1029,6 +1029,18 @@ dependencies = [
]
[[package]]
+name = "network-interface"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3329f515506e4a2de3aa6e07027a6758e22e0f0e8eaf64fa47261cec2282602"
+dependencies = [
+ "cc",
+ "libc",
+ "thiserror",
+ "winapi",
+]
+
+[[package]]
name = "nix"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1081,6 +1093,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
+name = "once_cell_polyfill"
+version = "1.70.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
+
+[[package]]
name = "openssl"
version = "0.10.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1352,9 +1370,9 @@ dependencies = [
[[package]]
name = "rustversion"
-version = "1.0.20"
+version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
+checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
[[package]]
name = "ryu"
@@ -1605,6 +1623,26 @@ dependencies = [
]
[[package]]
+name = "thiserror"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "time"
version = "0.3.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1647,9 +1685,9 @@ dependencies = [
[[package]]
name = "tokio"
-version = "1.45.0"
+version = "1.45.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165"
+checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779"
dependencies = [
"backtrace",
"bytes",
@@ -1910,6 +1948,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d"
[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
name = "windows"
version = "0.57.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1933,15 +1993,15 @@ dependencies = [
[[package]]
name = "windows-core"
-version = "0.61.1"
+version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "46ec44dc15085cea82cf9c78f85a9114c463a369786585ad2882d1ff0b0acf40"
+checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
dependencies = [
"windows-implement 0.60.0",
"windows-interface 0.59.1",
"windows-link",
- "windows-result 0.3.3",
- "windows-strings 0.4.1",
+ "windows-result 0.3.4",
+ "windows-strings 0.4.2",
]
[[package]]
@@ -2000,7 +2060,7 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3"
dependencies = [
- "windows-result 0.3.3",
+ "windows-result 0.3.4",
"windows-strings 0.3.1",
"windows-targets 0.53.0",
]
@@ -2016,9 +2076,9 @@ dependencies = [
[[package]]
name = "windows-result"
-version = "0.3.3"
+version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4b895b5356fc36103d0f64dd1e94dfa7ac5633f1c9dd6e80fe9ec4adef69e09d"
+checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
dependencies = [
"windows-link",
]
@@ -2034,9 +2094,9 @@ dependencies = [
[[package]]
name = "windows-strings"
-version = "0.4.1"
+version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2a7ab927b2637c19b3dbe0965e75d8f2d30bdd697a1516191cad2ec4df8fb28a"
+checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
dependencies = [
"windows-link",
]
diff --git a/Cargo.toml b/Cargo.toml
index de42a63..2262aa4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,9 +6,18 @@ description = "A command line interface (CLI) tool for syncing public WAN ip add
repository = "https://github.com/guspower/multiwan-dyndns"
license = "MIT OR Apache-2.0"
+# Both a library and a binary
+[[bin]]
+name = "multiwan-dyndns"
+path = "src/main.rs"
+
+[lib]
+name = "multiwan_dyndns"
+path = "src/lib.rs"
+
[dependencies]
reqwest = "0.12.15"
-tokio = { version = "1.45.0", features = ["rt", "rt-multi-thread", "macros"] }
+tokio = { version = "1.45.1", features = ["rt", "rt-multi-thread", "macros"] }
http = { version = "1.3.1" }
serde_json = "1.0.140"
serde = { version = "1.0.219", features = ["derive"] }
@@ -19,6 +28,10 @@ env_logger = "0.11.8"
clap = { version = "4.5.38", features = ["derive", "string"] }
log = "0.4.27"
homedir = "0.3.4"
+network-interface = "2.0.1"
[dev-dependencies]
wiremock = "0.6.3"
+
+[profile.dev]
+debug = false
diff --git a/src/error.rs b/src/error.rs
index 1ce9482..1b71511 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -1,4 +1,5 @@
use homedir::GetHomeError;
+use network_interface::Error as NetworkInterfaceError;
use reqwest::{Error as ReqwestError, Url};
use serde_json::Error as JsonError;
use std::fmt;
@@ -16,6 +17,7 @@ pub enum AppError {
InvalidResponse { url: Url, reason: String },
InvalidHttpHeader(String),
UnableToGetHomeDirectory(GetHomeError),
+ NetworkInterfaceError(NetworkInterfaceError),
}
impl fmt::Display for AppError {
@@ -34,11 +36,16 @@ impl fmt::Display for AppError {
Self::RequestFailed { url, .. } => write!(f, "Request to {} failed", url),
Self::InvalidResponse { url, reason } => {
write!(f, "Invalid response from {}: {}", url, reason)
- },
- Self::InvalidHttpHeader(message) => write!(f, "Invalid HTTP header configuration: {}", message),
+ }
+ Self::InvalidHttpHeader(message) => {
+ write!(f, "Invalid HTTP header configuration: {}", message)
+ }
Self::UnableToGetHomeDirectory(err) => {
write!(f, "Failed to get home directory: {}", err)
}
+ Self::NetworkInterfaceError(err) => {
+ write!(f, "Network interface error: {}", err)
+ }
}
}
}
@@ -49,6 +56,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),
_ => None,
}
}
diff --git a/src/http.rs b/src/http.rs
index db5e3a6..4ea9b96 100644
--- a/src/http.rs
+++ b/src/http.rs
@@ -1,9 +1,9 @@
-use serde_with::DisplayFromStr;
+use crate::error::{AppError, AppResult};
use http::{HeaderMap, HeaderName, HeaderValue, Method};
use reqwest::{Client, Request, Url};
use serde::{Deserialize, Serialize};
+use serde_with::DisplayFromStr;
use serde_with::serde_as;
-use crate::error::{AppError, AppResult};
#[serde_as]
#[derive(Debug, Serialize, Deserialize, PartialEq)]
@@ -18,9 +18,12 @@ pub struct RequestConfig {
}
impl RequestConfig {
-
pub fn new(method: Method, url: Url, headers: Vec<(String, String)>) -> Self {
- Self { method, url, headers }
+ Self {
+ method,
+ url,
+ headers,
+ }
}
pub fn get(url: Url, headers: Vec<(String, String)>) -> Self {
@@ -32,6 +35,8 @@ impl RequestConfig {
if !self.headers.is_empty() {
builder = builder.headers(self.header_map()?);
}
+ builder = builder.body("OHAI");
+
Ok(builder.build()?)
}
@@ -43,25 +48,30 @@ impl RequestConfig {
let mut header_map = HeaderMap::new();
for (name, value) in &self.headers {
let name: HeaderName = name.try_into().map_err(|e| {
- AppError::InvalidHttpHeader(format!("[{}] is not a valid HTTP header name ({})", name, e))
+ AppError::InvalidHttpHeader(format!(
+ "[{}] is not a valid HTTP header name ({})",
+ name, e
+ ))
})?;
let value: HeaderValue = value.parse().map_err(|e| {
- AppError::InvalidHttpHeader(format!("[{}] is not a valid HTTP header value ({})", value, e))
+ AppError::InvalidHttpHeader(format!(
+ "[{}] is not a valid HTTP header value ({})",
+ value, e
+ ))
})?;
header_map.insert(name, value);
}
Ok(header_map)
}
-
}
#[cfg(test)]
mod tests {
+ use super::*;
use crate::AppError;
use crate::{assert_error, assert_invalid_http_header, test};
use reqwest::ClientBuilder;
- use super::*;
test! {
fn simple_get_request() {
@@ -73,6 +83,25 @@ mod tests {
}
test! {
+ fn get_with_arbitrary_header() {
+ let url = Url::parse("https://example.com")?;
+ let actual = build_get_request(&url, vec![("X-Arbitrary".to_string(), "X-Arbitrary-Value".to_string())])?;
+ assert_eq!(actual.headers().get("X-Arbitrary").unwrap().to_str()?, "X-Arbitrary-Value");
+ }
+ }
+
+ test! {
+ fn post_request_with_text_body() {
+ let url = Url::parse("https://example.com")?;
+ let config = RequestConfig::new(Method::POST, url, vec![]);
+ let client = ClientBuilder::new().build()?;
+ let request = config.build(&client)?;
+
+ assert_eq!(request.method(), Method::POST);
+ }
+ }
+
+ test! {
fn invalid_header_name() {
let url = Url::parse("https://example.com")?;
let invalid_headers = vec![("Content\x00Type".to_string(), "application/json".to_string())];
@@ -93,5 +122,4 @@ mod tests {
let client = ClientBuilder::new().build()?;
config.build(&client)
}
-
}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..083cf46
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,11 @@
+mod config;
+mod dyndns_service;
+mod error;
+mod http;
+mod ip_service;
+
+#[cfg(test)]
+#[macro_use]
+pub mod test_macros;
+
+pub use error::{AppError, AppResult};
diff --git a/src/main.rs b/src/main.rs
index 2843eb3..3db2582 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -12,10 +12,11 @@ mod config;
mod dyndns_service;
mod error;
mod ip_service;
+mod network;
+mod http;
#[cfg(test)]
mod test_macros;
-mod http;
#[derive(Parser, Debug)]
#[command(ignore_errors(true), version, about, long_about = None)]
diff --git a/src/network.rs b/src/network.rs
new file mode 100644
index 0000000..3697e29
--- /dev/null
+++ b/src/network.rs
@@ -0,0 +1,93 @@
+use crate::error::{AppError, AppResult};
+use network_interface::{NetworkInterface, NetworkInterfaceConfig};
+use std::fmt;
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct NetworkAdapter {
+ pub name: String,
+ pub is_up: bool,
+ pub is_loopback: bool,
+}
+
+impl fmt::Display for NetworkAdapter {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{}", self.name)
+ }
+}
+
+impl From<NetworkInterface> for NetworkAdapter {
+ fn from(interface: NetworkInterface) -> Self {
+ let is_loopback = interface.name.to_lowercase().contains("lo");
+ let is_up = !interface.addr.is_empty();
+
+ Self {
+ name: interface.name,
+ is_up,
+ is_loopback,
+ }
+ }
+}
+
+pub fn get_network_adapters() -> AppResult<Vec<NetworkAdapter>> {
+ NetworkInterface::show()
+ .map_err(AppError::NetworkInterfaceError)
+ .map(|interfaces| interfaces.into_iter().map(NetworkAdapter::from).collect())
+}
+
+pub fn get_active_network_adapters() -> AppResult<Vec<NetworkAdapter>> {
+ get_network_adapters().map(|adapters| {
+ adapters
+ .into_iter()
+ .filter(|adapter| adapter.is_up && !adapter.is_loopback)
+ .collect()
+ })
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::test;
+
+ test! {
+ fn test_get_network_adapters() {
+ let adapters = get_network_adapters()?;
+
+ assert!(!adapters.is_empty(), "No network adapters found");
+
+ for adapter in &adapters {
+ assert!(!adapter.name.is_empty(), "Found adapter with empty name");
+ }
+
+ let up_adapters = adapters.iter().filter(|a| a.is_up).count();
+ assert!(up_adapters > 0, "No adapters are up");
+
+ let non_loopback = adapters.iter().filter(|a| !a.is_loopback).count();
+ assert!(non_loopback > 0, "All adapters are loopback interfaces");
+ }
+ }
+
+ test! {
+ fn test_get_active_network_adapters() {
+ let adapters = get_active_network_adapters()?;
+
+ assert!(!adapters.is_empty(), "No active network adapters found");
+
+ for adapter in &adapters {
+ assert!(adapter.is_up, "Found inactive adapter: {}", adapter.name);
+ assert!(!adapter.is_loopback, "Found loopback adapter: {}", adapter.name);
+ }
+ }
+ }
+
+ test! {
+ fn test_network_adapter_display() {
+ let adapter = NetworkAdapter {
+ name: "eth0".to_string(),
+ is_up: true,
+ is_loopback: false,
+ };
+
+ assert_eq!(format!("{}", adapter), "eth0");
+ }
+ }
+}