use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::time::{Duration, SystemTime};
use std::sync::Arc;
use tokio::sync::{RwLock, Mutex};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use tracing::{debug, info, warn, error};
pub mod protocol;
pub use protocol::{
UnixServer, ServerConfig, DaemonClient, UnixClient, ClientConfig,
JsonRpcRequest, JsonRpcResponse, JsonRpcError, ProtocolError, ProtocolResult,
};
mod v4_client;
mod v6_client;
mod pxe;
mod ddns;
mod tftp_client;
mod failover;
mod security;
mod config;
mod raw_socket;
mod wifi;
pub mod control;
pub use v4_client::*;
pub use v6_client::*;
pub use pxe::*;
pub use ddns::*;
pub use tftp_client::*;
pub use failover::*;
pub use security::*;
pub use config::*;
pub use raw_socket::*;
pub use wifi::*;
pub use control::*;
#[derive(Error, Debug)]
pub enum DhcpClientError {
#[error("Network error: {0}")]
Network(#[from] std::io::Error),
#[error("Timeout: {0}")]
Timeout(String),
#[error("Invalid message: {0}")]
InvalidMessage(String),
#[error("Invalid configuration: {0}")]
InvalidConfig(String),
#[error("Server error: {0}")]
ServerError(String),
#[error("Security violation: {0}")]
SecurityViolation(String),
#[error("No lease available")]
NoLease,
#[error("Interface not found: {0}")]
InterfaceNotFound(String),
#[error("Invalid interface name: {0}")]
InvalidInterfaceName(String),
#[error("DNS update failed: {0}")]
DnsUpdateFailed(String),
#[error("TFTP transfer failed: {0}")]
TftpFailed(String),
#[error("PXE error: {0}")]
PxeError(String),
#[error("WiFi error: {0}")]
WifiError(String),
}
impl From<anyhow::Error> for DhcpClientError {
fn from(err: anyhow::Error) -> Self {
DhcpClientError::WifiError(err.to_string())
}
}
pub type Result<T> = std::result::Result<T, DhcpClientError>;
pub fn validate_interface_name(name: &str) -> Result<()> {
if name.is_empty() {
return Err(DhcpClientError::InvalidInterfaceName(
"Interface name cannot be empty".to_string(),
));
}
if name.len() > 15 {
return Err(DhcpClientError::InvalidInterfaceName(
format!("Interface name too long (max 15 chars): {}", name),
));
}
if !name.chars().all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_') {
return Err(DhcpClientError::InvalidInterfaceName(
format!("Interface name contains invalid characters: {}", name),
));
}
Ok(())
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DhcpServer {
pub address: IpAddr,
pub last_response: Option<SystemTime>,
pub response_time: Option<Duration>,
pub failures: u32,
pub healthy: bool,
}
impl DhcpServer {
pub fn new(address: IpAddr) -> Self {
Self {
address,
last_response: None,
response_time: None,
failures: 0,
healthy: true,
}
}
pub fn mark_success(&mut self, response_time: Duration) {
self.last_response = Some(SystemTime::now());
self.response_time = Some(response_time);
self.failures = 0;
self.healthy = true;
}
pub fn mark_failure(&mut self) {
self.failures += 1;
if self.failures >= 3 {
self.healthy = false;
}
}
}
#[derive(Debug)]
pub struct DhcpClientManager {
v4_clients: Arc<RwLock<Vec<Arc<Dhcpv4Client>>>>,
v6_clients: Arc<RwLock<Vec<Arc<Dhcpv6Client>>>>,
config: Arc<RwLock<DhcpClientConfig>>,
running: Arc<Mutex<bool>>,
}
impl DhcpClientManager {
pub fn new(config: DhcpClientConfig) -> Self {
Self {
v4_clients: Arc::new(RwLock::new(Vec::new())),
v6_clients: Arc::new(RwLock::new(Vec::new())),
config: Arc::new(RwLock::new(config)),
running: Arc::new(Mutex::new(false)),
}
}
pub async fn start_interface(&self, interface: &str) -> Result<()> {
let config = self.config.read().await;
if !config.enabled {
return Err(DhcpClientError::InvalidConfig("DHCP client is disabled".to_string()));
}
if config.dhcpv4.enabled {
let wifi_config = if config.wifi.auto_connect {
config.wifi.networks.get(interface).cloned()
} else {
None
};
let v4_client = Dhcpv4Client::new(
interface,
config.dhcpv4.clone(),
config.security.clone(),
wifi_config,
)?;
let v4_client = Arc::new(v4_client);
let client_clone = v4_client.clone();
let interface_clone = interface.to_string();
tokio::spawn(async move {
if let Err(e) = client_clone.run().await {
error!("DHCPv4 client error on {}: {}", interface_clone, e);
}
});
self.v4_clients.write().await.push(v4_client);
}
if config.dhcpv6.enabled {
let v6_client = Dhcpv6Client::new(interface, config.dhcpv6.clone(), config.security.clone())?;
let v6_client = Arc::new(v6_client);
let client_clone = v6_client.clone();
let interface_clone = interface.to_string();
tokio::spawn(async move {
if let Err(e) = client_clone.run().await {
error!("DHCPv6 client error on {}: {}", interface_clone, e);
}
});
self.v6_clients.write().await.push(v6_client);
}
*self.running.lock().await = true;
info!("Started DHCP client on interface {}", interface);
Ok(())
}
pub async fn stop_interface(&self, interface: &str) -> Result<()> {
let mut v4_clients = self.v4_clients.write().await;
v4_clients.retain(|client| {
if client.interface() == interface {
false
} else {
true
}
});
let mut v6_clients = self.v6_clients.write().await;
v6_clients.retain(|client| {
if client.interface() == interface {
false
} else {
true
}
});
info!("Stopped DHCP client on interface {}", interface);
Ok(())
}
pub async fn get_status(&self) -> DhcpClientStatus {
let v4_clients = self.v4_clients.read().await;
let v6_clients = self.v6_clients.read().await;
let v4_status: Vec<Dhcpv4Status> = v4_clients.iter()
.map(|c| c.get_status())
.collect();
let v6_status: Vec<Dhcpv6Status> = v6_clients.iter()
.map(|c| c.get_status())
.collect();
DhcpClientStatus {
running: *self.running.lock().await,
v4_clients: v4_status,
v6_clients: v6_status,
}
}
pub async fn renew_interface(&self, interface: &str) -> Result<()> {
let v4_clients = self.v4_clients.read().await;
for client in v4_clients.iter() {
if client.interface() == interface {
client.renew().await?;
}
}
let v6_clients = self.v6_clients.read().await;
for client in v6_clients.iter() {
if client.interface() == interface {
client.renew().await?;
}
}
Ok(())
}
pub async fn release_interface(&self, interface: &str) -> Result<()> {
let v4_clients = self.v4_clients.read().await;
for client in v4_clients.iter() {
if client.interface() == interface {
client.release().await?;
}
}
let v6_clients = self.v6_clients.read().await;
for client in v6_clients.iter() {
if client.interface() == interface {
client.release().await?;
}
}
Ok(())
}
pub async fn get_config(&self) -> DhcpClientConfig {
self.config.read().await.clone()
}
pub async fn set_config(&self, config: DhcpClientConfig) -> Result<()> {
*self.config.write().await = config;
Ok(())
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct DhcpClientStatus {
pub running: bool,
pub v4_clients: Vec<Dhcpv4Status>,
pub v6_clients: Vec<Dhcpv6Status>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Dhcpv4Status {
pub interface: String,
pub state: String,
pub lease: Option<Dhcpv4LeaseInfo>,
pub servers: Vec<DhcpServer>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Dhcpv6Status {
pub interface: String,
pub state: String,
pub ia_na: Option<Dhcpv6IaNaInfo>,
pub ia_pd: Option<Dhcpv6IaPdInfo>,
pub servers: Vec<DhcpServer>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Dhcpv4LeaseInfo {
pub ip_address: Ipv4Addr,
pub subnet_mask: Ipv4Addr,
pub router: Option<Ipv4Addr>,
pub dns_servers: Vec<Ipv4Addr>,
pub domain_name: Option<String>,
pub ntp_servers: Vec<Ipv4Addr>,
pub lease_time: Duration,
pub renewal_time: Duration,
pub rebinding_time: Duration,
pub acquired_at: SystemTime,
pub expires_at: SystemTime,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Dhcpv6IaNaInfo {
pub addresses: Vec<Ipv6Addr>,
pub preferred_lifetime: Duration,
pub valid_lifetime: Duration,
pub acquired_at: SystemTime,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Dhcpv6IaPdInfo {
pub prefix: Ipv6Addr,
pub prefix_len: u8,
pub preferred_lifetime: Duration,
pub valid_lifetime: Duration,
pub acquired_at: SystemTime,
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_dhcp_client_manager_creation() {
let config = DhcpClientConfig::default();
let manager = DhcpClientManager::new(config);
let status = manager.get_status().await;
assert!(!status.running);
assert_eq!(status.v4_clients.len(), 0);
assert_eq!(status.v6_clients.len(), 0);
}
#[test]
fn test_dhcp_server() {
let mut server = DhcpServer::new(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)));
assert!(server.healthy);
assert_eq!(server.failures, 0);
server.mark_failure();
assert_eq!(server.failures, 1);
assert!(server.healthy);
server.mark_failure();
server.mark_failure();
assert_eq!(server.failures, 3);
assert!(!server.healthy);
server.mark_success(Duration::from_millis(10));
assert_eq!(server.failures, 0);
assert!(server.healthy);
}
}