1use super::Config;
2use crate::v4::error::Result;
3use ip_network::Ipv4Network;
4use log::{debug, warn};
5use mac_address::MacAddress;
6use serde::{Deserialize, Serialize};
7use std::fs::{read_to_string, rename, write};
8use std::path::PathBuf;
9use std::time::{Duration, Instant, SystemTime};
10use std::{
11 collections::{HashMap, HashSet},
12 net::Ipv4Addr,
13};
14
15#[derive(Deserialize, Serialize, Clone, Debug)]
16enum LeaseStatus {
17 Offered,
18 Acked,
19}
20
21#[derive(Deserialize, Serialize, Clone, Debug)]
22struct Lease {
23 ip: Ipv4Addr,
24 status: LeaseStatus,
25 is_static: bool,
26 when: SystemTime,
28}
29
30impl Lease {
31 #[inline]
32 fn new(ip: Ipv4Addr, status: LeaseStatus, is_static: bool) -> Self {
33 Self {
34 ip,
35 status,
36 is_static,
37 when: SystemTime::now(),
38 }
39 }
40
41 #[inline]
43 fn extend(&mut self) {
44 self.when = SystemTime::now();
45 }
46
47 fn is_expired(&self, lease_time: u32) -> Result<bool> {
48 match self.when.elapsed() {
49 Ok(elapsed) => Ok(elapsed >= Duration::from_secs(lease_time as u64)),
50 Err(_) => Err("Problem getting elapsed system time"),
51 }
52 }
53}
54
55#[derive(Deserialize, Serialize, Debug)]
57pub struct Leases {
58 #[serde(skip)]
59 available: HashSet<Ipv4Addr>,
60 leased: HashMap<MacAddress, Lease>,
61 #[serde(skip)]
64 static_leases: HashMap<MacAddress, Ipv4Addr>,
65 lease_time: u32,
67 #[serde(skip)]
70 config_path: Option<PathBuf>,
71 #[serde(skip)]
74 last_write: Option<Instant>,
75}
76
77impl Leases {
78 pub const FILE_NAME: &'static str = "toe-beans-leases.toml";
80
81 const OFFER_TIMEOUT: u32 = 300;
88
89 pub fn new(config: &Config) -> Self {
92 let empty_leases = || {
93 debug!("Not using leases file");
94 let size = config.network_cidr.hosts().len();
95 Self {
96 available: HashSet::with_capacity(size),
97 leased: HashMap::with_capacity(size),
98 static_leases: HashMap::new(),
99 lease_time: config.lease_time.as_server(),
100 config_path: None,
101 last_write: None,
102 }
103 };
104
105 let mut leases = if config.use_leases_file {
106 let mut leases = Self::read_leases(config).unwrap_or_else(|_| empty_leases());
107 leases.config_path = Some(config.path.clone());
108 leases.last_write = Some(Instant::now());
109 leases
110 } else {
111 empty_leases()
112 };
113
114 leases.static_leases = config.static_leases.clone();
115 leases.fill_available(&config.network_cidr);
116
117 leases
118 }
119
120 fn read_leases(config: &Config) -> Result<Leases> {
121 debug!("Trying to read, parse, and validate leases file");
122 let read_result = read_to_string(config.path.join(Self::FILE_NAME));
123
124 let leases: Leases = match read_result {
125 Ok(toml_string) => {
126 let toml_result = toml::from_str(&toml_string);
127 match toml_result {
128 Ok(leases) => leases,
129 Err(_) => return Err("Problem parsing leases file"),
130 }
131 }
132 Err(_) => {
133 return Err("Problem reading leases file");
134 }
135 };
136
137 leases.validate(&config.network_cidr)?;
138
139 Ok(leases)
140 }
141
142 #[inline]
146 fn validate(&self, network: &Ipv4Network) -> Result<()> {
147 let broadcast_address = network.broadcast_address();
148 let network_address = network.network_address();
149 let static_lease_ips = self.static_leases.values();
150 let leased_ips = self.leased.values().map(|lease| &lease.ip);
151
152 static_lease_ips.chain(leased_ips).try_for_each(|ip| {
153 if !network.contains(*ip) {
154 return Err("The configured network range does not include the restored ip. Did the network range change?");
155 }
156
157 if ip == &broadcast_address {
158 return Err("The network's broadcast address can't be leased");
159 }
160
161 if ip == &network_address {
162 return Err("The network's network address can't be leased");
163 }
164
165 Ok(())
166 })?;
167
168 Ok(())
169 }
170
171 fn fill_available(&mut self, network: &Ipv4Network) {
173 let hosts = network.hosts();
174
175 self.available.extend(hosts);
176
177 self.available.remove(&network.broadcast_address());
179 self.available.remove(&network.network_address());
180
181 self.leased.values().for_each(|lease| {
183 self.available.remove(&lease.ip);
184 });
185
186 self.static_leases.values().for_each(|ip| {
188 self.available.remove(ip);
189 });
190 }
191
192 fn commit(&mut self) -> Result<()> {
194 if self.config_path.is_none() {
196 return Ok(());
197 }
198
199 if self
200 .last_write
201 .expect("This should always be Some at this point")
202 .elapsed()
203 < Duration::from_secs(10)
204 {
206 return Ok(());
207 }
208
209 debug!("Attempting to write {}", Self::FILE_NAME);
210
211 match toml::to_string_pretty(&self) {
212 Ok(file_content) => {
213 let file_name = self
214 .config_path
215 .as_mut()
216 .expect("This should always be Some at this point")
217 .join(Self::FILE_NAME);
218 let mut temp_file_name = file_name.clone();
219 temp_file_name.set_extension("toml.tmp");
220
221 if write(&temp_file_name, file_content.as_bytes()).is_err() {
222 return Err("Failed to write to temporary leases file");
223 }
224
225 debug!("Temporary file written successfully");
226
227 if rename(temp_file_name, file_name).is_err() {
228 return Err("Failed to replace leases file with temporary leases file");
229 }
230
231 debug!("Temporary file replaced original file successfully");
232 }
233 Err(_) => return Err("Failed to generate toml content for leases file"),
234 };
235
236 self.last_write = Some(Instant::now());
237 Ok(())
238 }
239
240 pub fn offer(&mut self, owner: MacAddress, requested_ip: Option<Ipv4Addr>) -> Result<Ipv4Addr> {
250 if let Some(lease) = self.leased.get_mut(&owner) {
251 match lease.status {
252 LeaseStatus::Offered => {
253 warn!(
254 "{} will be offered an IP address that it has already been offered",
255 owner
256 );
257 lease.extend();
258 return Ok(lease.ip);
259 }
260 LeaseStatus::Acked => {
261 if !lease.is_expired(self.lease_time)? {
262 warn!(
263 "{} will be offered its non-expired, acked IP address again",
264 owner
265 );
266 lease.status = LeaseStatus::Offered;
267 lease.extend();
268 return Ok(lease.ip);
269 }
270 }
272 }
273 }
274
275 if let Some(requested_ip) = requested_ip {
276 let not_available = !self.available.contains(&requested_ip);
277 let has_static_lease = self.static_leases.contains_key(&owner);
278
279 if not_available {
280 debug!("Requested IP Address is not available (or maybe not in network range)");
282 } else if has_static_lease {
283 debug!("Requested IP Address ignored because owner has static lease");
284 } else {
285 self.available.remove(&requested_ip);
286 self.leased
287 .insert(owner, Lease::new(requested_ip, LeaseStatus::Offered, false));
288 return Ok(requested_ip);
289 }
290 }
291
292 let lease = self.get_lease(&owner, LeaseStatus::Offered)?;
294 let ip = lease.ip;
295 self.leased.insert(owner, lease);
296 Ok(ip)
297 }
298
299 pub fn ack(&mut self, owner: MacAddress) -> Result<Ipv4Addr> {
302 let maybe_leased = self.leased.get_mut(&owner);
303 let ip = match maybe_leased {
304 Some(leased) => {
305 match leased.status {
306 LeaseStatus::Offered => {
307 leased.status = LeaseStatus::Acked;
308 }
309 LeaseStatus::Acked => {
310 warn!(
311 "{} will be leased an IP address it was already leased",
312 owner
313 );
314 }
315 };
316
317 leased.extend();
318 leased.ip
319 }
320 None => {
321 let lease = self.get_lease(&owner, LeaseStatus::Acked)?;
323 let ip = lease.ip;
324 self.leased.insert(owner, lease);
325 ip
326 }
327 };
328
329 self.commit()?;
330
331 Ok(ip)
332 }
333
334 pub fn extend(&mut self, owner: MacAddress) -> Result<()> {
337 match self.leased.get_mut(&owner) {
338 Some(lease) => {
339 lease.extend();
340 Ok(())
341 }
342 None => Err("No IP address lease found with that owner"),
343 }
344 }
345
346 pub fn release(&mut self, owner: MacAddress) -> Result<()> {
348 let maybe_leased = self.leased.remove(&owner);
349
350 match maybe_leased {
351 Some(lease) => {
352 if !lease.is_static {
353 self.available.insert(lease.ip);
354 }
355 Ok(())
356 }
357 None => Err("No IP address lease found with that owner"),
358 }
359 }
360
361 fn get_lease(&mut self, owner: &MacAddress, status: LeaseStatus) -> Result<Lease> {
363 let lease = match self.static_leases.get(owner) {
364 Some(ip) => Lease::new(*ip, status, true),
365 None => Lease::new(self.get_ip()?, status, false),
366 };
367 Ok(lease)
368 }
369
370 fn get_ip(&mut self) -> Result<Ipv4Addr> {
375 if let Some(any) = self.available.iter().next().cloned() {
376 debug!("Chose available IP address {any}");
377 return Ok(self.available.take(&any).unwrap());
379 }
380
381 let maybe_expired_lease = self
382 .leased
383 .clone() .into_iter() .find(|(_owner, lease)| {
386 let expiration = match lease.status {
387 LeaseStatus::Offered => Self::OFFER_TIMEOUT,
388 LeaseStatus::Acked => self.lease_time,
389 };
390 lease.is_expired(expiration).unwrap_or(false)
391 });
392
393 if let Some(expired_lease) = maybe_expired_lease {
394 debug!("Reusing expired lease's IP address");
395 return Ok(self.leased.remove(&expired_lease.0).unwrap().ip);
397 }
398
399 Err("No more IP addresses available")
400 }
401
402 pub fn is_available(&self, ip: &Ipv4Addr) -> bool {
404 self.available.contains(ip)
405 }
406
407 pub fn verify_lease(&self, owner: MacAddress, ip: &Ipv4Addr) -> Result<()> {
410 let maybe_leased = self.leased.get(&owner);
411
412 match maybe_leased {
413 Some(lease) => {
414 if let LeaseStatus::Offered = lease.status {
415 return Err("Lease offered but not previously acked");
416 }
417
418 if &lease.ip != ip {
419 return Err("Client's notion of ip address is wrong");
420 }
421
422 if lease.is_expired(self.lease_time)? {
423 return Err("Lease has expired");
424 }
425
426 Ok(())
427 }
428 None => Err("No IP address lease found with that owner"),
429 }
430 }
431}