toe_beans/v4/server/leases/
mod.rs1mod config;
2mod lease;
3mod lease_time;
4
5pub use config::*;
6pub use lease_time::*;
7
8use crate::v4::error::Result;
9use ip_network::Ipv4Network;
10use lease::{Lease, LeaseStatus, PAGE_SIZE};
11use mac_address::MacAddress;
12use std::fs::{File, OpenOptions};
13use std::io::{BufRead, BufReader, Seek, SeekFrom, Write};
14use std::{
15 collections::{HashMap, HashSet},
16 net::Ipv4Addr,
17};
18use tracing::{debug, error, info, warn};
19
20#[derive(Debug)]
26pub struct Leases {
27 available: HashSet<Ipv4Addr>,
28 leased: HashMap<MacAddress, Lease>,
29 lease_time: u32,
31 lines: u32,
33 config: LeaseConfig,
35 file: Option<File>,
40}
41
42impl Leases {
43 pub const FILE_NAME: &'static str = "toe-beans.leases";
45
46 const OFFER_TIMEOUT: u32 = 300;
53
54 #[inline]
57 pub fn get_config(&self) -> &LeaseConfig {
58 &self.config
59 }
60
61 fn empty(config: &LeaseConfig) -> Self {
62 let size = config.network_cidr.hosts().len();
63 Self {
64 available: HashSet::with_capacity(size),
65 leased: HashMap::with_capacity(size),
66 lease_time: config.lease_time.as_server(),
67 config: LeaseConfig::default(),
68 lines: 0,
69 file: None,
70 }
71 }
72
73 pub fn new(config: LeaseConfig) -> Self {
76 let file_path = config.leases_file_path.join(Self::FILE_NAME);
77
78 let mut leases = if config.use_leases_file {
79 let mut leases = Self::read_leases(&config).unwrap_or_else(|_| Self::empty(&config));
80
81 let file = OpenOptions::new()
82 .write(true)
83 .create(true)
84 .truncate(false)
85 .open(file_path)
86 .expect("Problem opening leases file for writing");
87 file.lock().expect("Failed to acquire lock on leases file");
88 leases.file = Some(file);
89
90 leases
91 } else {
92 Self::empty(&config)
93 };
94
95 leases.config = config;
96 leases.fill_available();
97
98 leases
99 }
100
101 fn read_leases(config: &LeaseConfig) -> Result<Leases> {
102 info!("Trying to read, parse, and validate leases file");
103
104 let full_leases_file_path = config.leases_file_path.join(Self::FILE_NAME);
105 let open_result = File::open(full_leases_file_path);
106 let leased: HashMap<MacAddress, Lease> = match open_result {
107 Ok(file) => {
108 let mut line_num = 0;
109 BufReader::new(file)
110 .lines()
111 .map(|line| {
112 let lease_string = line.expect("Problem reading leases file");
113 line_num += 1;
114 Lease::from_string(lease_string, line_num)
115 .expect("Problem parsing leases file")
116 })
117 .collect()
118 }
119 Err(message) => {
120 error!("Problem opening leases file: {}", message);
121 return Err("Problem opening leases file");
122 }
123 };
124
125 let mut leases = Leases::empty(config);
126 leases.leased = leased;
127
128 leases.validate(&config.network_cidr)?;
129
130 info!("Restored and validated leases file");
131
132 Ok(leases)
133 }
134
135 #[inline]
139 fn validate(&self, network: &Ipv4Network) -> Result<()> {
140 let broadcast_address = network.broadcast_address();
141 let network_address = network.network_address();
142 let static_lease_ips = self.config.static_leases.values();
143 let leased_ips = self.leased.values().map(|lease| &lease.ip);
144
145 static_lease_ips.chain(leased_ips).try_for_each(|ip| {
146 if !network.contains(*ip) {
147 return Err("The configured network range does not include the restored ip. Did the network range change?");
148 }
149
150 if ip == &broadcast_address {
151 return Err("The network's broadcast address can't be leased");
152 }
153
154 if ip == &network_address {
155 return Err("The network's network address can't be leased");
156 }
157
158 Ok(())
159 })?;
160
161 Ok(())
162 }
163
164 fn fill_available(&mut self) {
166 let network = self.config.network_cidr;
167 let hosts = network.hosts();
168
169 self.available.extend(hosts);
170
171 self.available.remove(&network.broadcast_address());
173 self.available.remove(&network.network_address());
174
175 self.leased.values().for_each(|lease| {
177 self.available.remove(&lease.ip);
178 });
179
180 self.config.static_leases.values().for_each(|ip| {
182 self.available.remove(ip);
183 });
184 }
185
186 fn commit(&mut self, mut lease: Lease, owner: MacAddress) -> Result<()> {
188 if !self.config.use_leases_file {
189 return Ok(());
190 }
191
192 if lease.line == 0 {
194 lease.line = self.lines + 1
195 }
196
197 let mut file_content: [u8; PAGE_SIZE] = [32; PAGE_SIZE];
200 lease
201 .to_string(owner)
202 .as_bytes()
203 .iter()
204 .enumerate()
205 .for_each(|(i, byte)| file_content[i] = *byte);
206 file_content[PAGE_SIZE - 1] = 10;
208
209 if self
210 .file
211 .as_mut()
212 .expect("This should always be Some here")
213 .seek(SeekFrom::Start((PAGE_SIZE * lease.line as usize) as u64))
214 .is_err()
215 {
216 return Err("Problem writing to leases file");
217 };
218
219 if self
220 .file
221 .as_mut()
222 .expect("This should always be Some here")
223 .write_all(&file_content)
224 .is_err()
225 {
226 return Err("Problem writing to leases file");
227 };
228
229 self.lines += 1;
230
231 Ok(())
232 }
233
234 pub fn offer(&mut self, owner: MacAddress, requested_ip: Option<Ipv4Addr>) -> Result<Ipv4Addr> {
244 if let Some(lease) = self.leased.get_mut(&owner) {
245 match lease.status {
246 LeaseStatus::Offered => {
247 warn!(
248 "{} will be offered an IP address that it has already been offered",
249 owner
250 );
251 lease.extend();
252 return Ok(lease.ip);
253 }
254 LeaseStatus::Acked => {
255 if !lease.is_expired(self.lease_time) {
256 warn!(
257 "{} will be offered its non-expired, acked IP address again",
258 owner
259 );
260 lease.status = LeaseStatus::Offered;
261 lease.extend();
262 return Ok(lease.ip);
263 }
264 }
266 }
267 }
268
269 if let Some(requested_ip) = requested_ip {
270 let not_available = !self.available.contains(&requested_ip);
271 let has_static_lease = self.config.static_leases.contains_key(&owner);
272
273 if not_available {
274 debug!("Requested IP Address is not available (or maybe not in network range)");
276 } else if has_static_lease {
277 debug!("Requested IP Address ignored because owner has static lease");
278 } else {
279 self.available.remove(&requested_ip);
280 self.leased
281 .insert(owner, Lease::new(requested_ip, LeaseStatus::Offered, false));
282 return Ok(requested_ip);
283 }
284 }
285
286 let lease = self.get_lease(&owner, LeaseStatus::Offered)?;
288 let ip = lease.ip;
289 self.leased.insert(owner, lease);
290 Ok(ip)
291 }
292
293 pub fn ack(&mut self, owner: MacAddress) -> Result<Ipv4Addr> {
296 let maybe_lease = self.leased.get_mut(&owner);
297 let lease = match maybe_lease {
298 Some(lease) => {
299 match lease.status {
300 LeaseStatus::Offered => {
301 lease.status = LeaseStatus::Acked;
302 }
303 LeaseStatus::Acked => {
304 warn!(
305 "{} will be leased an IP address it was already leased",
306 owner
307 );
308 }
309 };
310
311 lease.extend();
312 lease.to_owned()
313 }
314 None => {
315 let lease = self.get_lease(&owner, LeaseStatus::Acked)?;
317 self.leased.insert(owner, lease.clone());
318 lease
319 }
320 };
321
322 let ip = lease.ip;
323 self.commit(lease, owner)?;
324
325 Ok(ip)
326 }
327
328 pub fn extend(&mut self, owner: MacAddress) -> Result<()> {
331 match self.leased.get_mut(&owner) {
332 Some(lease) => {
333 lease.extend();
334 Ok(())
335 }
336 None => Err("No IP address lease found with that owner"),
337 }
338 }
339
340 pub fn release(&mut self, owner: MacAddress) -> Result<()> {
342 let maybe_leased = self.leased.remove(&owner);
343
344 match maybe_leased {
345 Some(lease) => {
346 if !lease.is_static {
347 self.available.insert(lease.ip);
348 }
349 Ok(())
350 }
351 None => Err("No IP address lease found with that owner"),
352 }
353 }
354
355 fn get_lease(&mut self, owner: &MacAddress, status: LeaseStatus) -> Result<Lease> {
357 let lease = match self.config.static_leases.get(owner) {
358 Some(ip) => Lease::new(*ip, status, true),
359 None => Lease::new(self.get_ip()?, status, false),
360 };
361 Ok(lease)
362 }
363
364 fn get_ip(&mut self) -> Result<Ipv4Addr> {
369 if let Some(any) = self.available.iter().next().cloned() {
370 debug!("Chose available IP address {any}");
371 return Ok(self.available.take(&any).unwrap());
373 }
374
375 let maybe_expired_lease = self
376 .leased
377 .clone() .into_iter() .find(|(_owner, lease)| {
380 let expiration = match lease.status {
381 LeaseStatus::Offered => Self::OFFER_TIMEOUT,
382 LeaseStatus::Acked => self.lease_time,
383 };
384 lease.is_expired(expiration)
385 });
386
387 if let Some(expired_lease) = maybe_expired_lease {
388 debug!("Reusing expired lease's IP address");
389 return Ok(self.leased.remove(&expired_lease.0).unwrap().ip);
391 }
392
393 Err("No more IP addresses available")
394 }
395
396 pub fn is_available(&self, ip: &Ipv4Addr) -> bool {
398 self.available.contains(ip)
399 }
400
401 pub fn verify_lease(&self, owner: MacAddress, ip: &Ipv4Addr) -> Result<()> {
404 let maybe_leased = self.leased.get(&owner);
405
406 match maybe_leased {
407 Some(lease) => {
408 if let LeaseStatus::Offered = lease.status {
409 return Err("Lease offered but not previously acked");
410 }
411
412 if &lease.ip != ip {
413 return Err("Client's notion of ip address is wrong");
414 }
415
416 if lease.is_expired(self.lease_time) {
417 return Err("Lease has expired");
418 }
419
420 Ok(())
421 }
422 None => Err("No IP address lease found with that owner"),
423 }
424 }
425}