diff options
author | HimbeerserverDE <himbeerserverde@gmail.com> | 2023-03-04 17:56:09 +0100 |
---|---|---|
committer | HimbeerserverDE <himbeerserverde@gmail.com> | 2023-03-04 17:56:09 +0100 |
commit | 0949b26177418518aaa61ea488cae7c2e6183cc8 (patch) | |
tree | f69fb54f1eca7b9ac4e3d79577e216518a0978f5 | |
parent | ea80252f062eb0d825f4ad49fb80e16578391420 (diff) |
add LeaseFileManager
leases can now be saved on persistent storage and are preserved across restarts
-rw-r--r-- | Cargo.lock | 43 | ||||
-rw-r--r-- | Cargo.toml | 3 | ||||
-rw-r--r-- | src/error.rs | 2 | ||||
-rw-r--r-- | src/lease.rs | 145 | ||||
-rw-r--r-- | src/main.rs | 32 |
5 files changed, 208 insertions, 17 deletions
@@ -63,6 +63,9 @@ dependencies = [ "ipnet", "linkaddrs", "rand", + "serde", + "serde_derive", + "serde_json", "socket2 0.5.1", "thiserror", ] @@ -250,6 +253,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146" [[package]] +name = "itoa" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" + +[[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -487,6 +496,40 @@ dependencies = [ ] [[package]] +name = "ryu" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" + +[[package]] +name = "serde" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" + +[[package]] +name = "serde_derive" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] name = "slab" version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -10,5 +10,8 @@ dhcproto = "0.8.0" ipnet = "2.7.1" linkaddrs = { git = "https://github.com/HimbeerserverDE/linkaddrs.git", version = "0.1.0" } rand = "0.8.5" +serde = "1.0" +serde_derive = "1.0" +serde_json = "1.0" socket2 = "0.5.1" thiserror = "1.0" diff --git a/src/error.rs b/src/error.rs index d6bf018..43378a8 100644 --- a/src/error.rs +++ b/src/error.rs @@ -29,6 +29,8 @@ pub enum Error { Io(#[from] io::Error), #[error("linkaddrs error")] LinkAddrs(#[from] linkaddrs::Error), + #[error("serde_json error")] + SerdeJson(#[from] serde_json::Error), } pub type Result<T> = std::result::Result<T, Error>; diff --git a/src/lease.rs b/src/lease.rs index 0369a72..4996f99 100644 --- a/src/lease.rs +++ b/src/lease.rs @@ -1,9 +1,14 @@ +use crate::error::Result; + +use std::fs::File; +use std::io::Seek; use std::net::Ipv4Addr; use std::time::{Duration, SystemTime}; use ipnet::Ipv4AddrRange; +use serde_derive::{Deserialize, Serialize}; -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Lease { pub address: Ipv4Addr, pub expires: SystemTime, @@ -25,10 +30,10 @@ impl Lease { pub trait LeaseManager { fn range(&self) -> (Ipv4Addr, Ipv4Addr); fn netmask(&self) -> Ipv4Addr; - fn leases(&self) -> Box<dyn Iterator<Item = Lease>>; - fn request(&mut self, address: Ipv4Addr, client_id: &[u8]) -> bool; fn lease_time(&self) -> Duration; - fn release(&mut self, client_id: &[u8]) -> Box<dyn Iterator<Item = Ipv4Addr>>; + fn leases(&self) -> Box<dyn Iterator<Item = Lease>>; + fn request(&mut self, address: Ipv4Addr, client_id: &[u8]) -> Result<bool>; + fn release(&mut self, client_id: &[u8]) -> Result<Box<dyn Iterator<Item = Ipv4Addr>>>; fn all_addresses(&self) -> Vec<Ipv4Addr> { let range = self.range(); @@ -137,6 +142,10 @@ impl LeaseManager for LeaseDummyManager { "255.255.255.0".parse().unwrap() } + fn lease_time(&self) -> Duration { + Duration::from_secs(300) + } + fn leases(&self) -> Box<dyn Iterator<Item = Lease>> { Box::new( self.leases @@ -146,9 +155,9 @@ impl LeaseManager for LeaseDummyManager { ) } - fn request(&mut self, address: Ipv4Addr, client_id: &[u8]) -> bool { + fn request(&mut self, address: Ipv4Addr, client_id: &[u8]) -> Result<bool> { if self.is_unavailable(address, client_id) { - false + Ok(false) } else { let lease = self .leases @@ -163,15 +172,130 @@ impl LeaseManager for LeaseDummyManager { self.leases .push(Lease::new(address, self.lease_time(), client_id.to_vec())); - true + Ok(true) } } + fn release(&mut self, client_id: &[u8]) -> Result<Box<dyn Iterator<Item = Ipv4Addr>>> { + let mut released = Vec::new(); + + self.leases + .clone() + .into_iter() + .enumerate() + .filter(|(_, lease)| lease.client_id == client_id) + .for_each(|(i, lease)| { + self.leases.remove(i); + released.push(lease.address); + }); + + Ok(Box::new(released.into_iter())) + } +} + +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +pub struct LeaseFileManagerConfig { + pub range: (Ipv4Addr, Ipv4Addr), + pub netmask: Ipv4Addr, + pub lease_time: Duration, +} + +#[derive(Debug)] +pub struct LeaseFileManager { + config: LeaseFileManagerConfig, + file: File, + leases: Vec<Lease>, +} + +impl LeaseFileManager { + pub fn new(config: LeaseFileManagerConfig, file: File) -> Result<Self> { + let mut mgr = Self { + config, + file, + leases: Vec::new(), + }; + + mgr.load()?; + Ok(mgr) + } + + fn load(&mut self) -> Result<()> { + self.file.rewind()?; + self.leases = serde_json::from_reader(&self.file)?; + + Ok(()) + } + + fn save(&mut self) -> Result<()> { + self.file.rewind()?; + self.file.set_len(0)?; + + serde_json::to_writer_pretty(&self.file, &self.leases)?; + Ok(()) + } + + fn garbage_collect(&mut self) -> Result<()> { + self.leases = self + .leases + .clone() + .into_iter() + .filter(|lease| SystemTime::now().duration_since(lease.expires).is_err()) + .collect(); + + self.save()?; + Ok(()) + } +} + +impl LeaseManager for LeaseFileManager { + fn range(&self) -> (Ipv4Addr, Ipv4Addr) { + self.config.range + } + + fn netmask(&self) -> Ipv4Addr { + self.config.netmask + } + fn lease_time(&self) -> Duration { - Duration::from_secs(300) + self.config.lease_time + } + + fn leases(&self) -> Box<dyn Iterator<Item = Lease>> { + Box::new( + self.leases + .clone() + .into_iter() + .filter(|lease| SystemTime::now().duration_since(lease.expires).is_err()), + ) } - fn release(&mut self, client_id: &[u8]) -> Box<dyn Iterator<Item = Ipv4Addr>> { + fn request(&mut self, address: Ipv4Addr, client_id: &[u8]) -> Result<bool> { + self.garbage_collect()?; + + if self.is_unavailable(address, client_id) { + Ok(false) + } else { + let lease = self + .leases + .iter() + .enumerate() + .find(|(_, lease)| lease.client_id == client_id); + + if let Some(lease) = lease { + self.leases.remove(lease.0); + } + + self.leases + .push(Lease::new(address, self.lease_time(), client_id.to_vec())); + + self.save()?; + Ok(true) + } + } + + fn release(&mut self, client_id: &[u8]) -> Result<Box<dyn Iterator<Item = Ipv4Addr>>> { + self.garbage_collect()?; + let mut released = Vec::new(); self.leases @@ -184,6 +308,7 @@ impl LeaseManager for LeaseDummyManager { released.push(lease.address); }); - Box::new(released.into_iter()) + self.save()?; + Ok(Box::new(released.into_iter())) } } diff --git a/src/main.rs b/src/main.rs index 99d467a..694a4f7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,18 +1,36 @@ use dhcp4d::error::{Error, Result}; -use dhcp4d::lease::{LeaseDummyManager, LeaseManager}; +use dhcp4d::lease::{LeaseFileManager, LeaseFileManagerConfig, LeaseManager}; use dhcp4d::util::{format_client_id, local_ip}; +use std::fs::OpenOptions; use std::mem::MaybeUninit; use std::net::{IpAddr, SocketAddr, SocketAddrV4}; use std::sync::{Arc, Mutex}; use std::thread; +use std::time::Duration; use dhcproto::v4::{DhcpOption, Flags, Message, MessageType, Opcode, OptionCode}; use dhcproto::{Decodable, Decoder, Encodable, Encoder}; use socket2::{Domain, Socket, Type}; fn main() -> Result<()> { - let lease_mgr = Arc::new(Mutex::new(LeaseDummyManager::new(None))); + let config = LeaseFileManagerConfig { + range: ( + "198.51.100.100".parse().unwrap(), + "198.51.100.249".parse().unwrap(), + ), + netmask: "255.255.255.0".parse().unwrap(), + lease_time: Duration::from_secs(300), + }; + + let file = OpenOptions::new() + .create(true) + .read(true) + .write(true) + .truncate(false) + .open("leases.json")?; + + let lease_mgr = Arc::new(Mutex::new(LeaseFileManager::new(config, file)?)); let mut threads = Vec::new(); for arg in std::env::args().skip(1) { @@ -27,7 +45,7 @@ fn main() -> Result<()> { Ok(()) } -fn run(link: String, lease_mgr: Arc<Mutex<LeaseDummyManager>>) -> Result<()> { +fn run<T: LeaseManager>(link: String, lease_mgr: Arc<Mutex<T>>) -> Result<()> { let sock = Socket::new(Domain::IPV4, Type::DGRAM, None)?; let addresses = linkaddrs::ipv4_addresses(link)?; @@ -56,9 +74,9 @@ fn run(link: String, lease_mgr: Arc<Mutex<LeaseDummyManager>>) -> Result<()> { } } -fn handle_request( +fn handle_request<T: LeaseManager>( sock: &Socket, - lease_mgr: Arc<Mutex<LeaseDummyManager>>, + lease_mgr: Arc<Mutex<T>>, buf: &[u8], remote: SocketAddrV4, ) -> Result<()> { @@ -144,7 +162,7 @@ fn handle_request( _ => unreachable!(), }; - if !lease_mgr.request(*requested_addr, client_id) { + if !lease_mgr.request(*requested_addr, client_id)? { let own_addr = local_ip(sock); let mut resp = Message::default(); @@ -225,7 +243,7 @@ fn handle_request( let mut lease_mgr = lease_mgr.lock().unwrap(); let released: Vec<String> = lease_mgr - .release(client_id) + .release(client_id)? .map(|addr| addr.to_string()) .collect(); |