toe_beans/v4/server/
leases.rs1use super::Config;
2use crate::v4::error::Result;
3use log::{debug, info, warn};
4use mac_address::MacAddress;
5use serde::{Deserialize, Serialize};
6use std::fs::{read_to_string, File};
7use std::io::Write;
8use std::os::unix::fs::OpenOptionsExt;
9use std::path::PathBuf;
10use std::time::{Duration, SystemTime};
11use std::{
12 collections::{HashMap, HashSet},
13 fs::OpenOptions,
14 net::Ipv4Addr,
15};
16
17#[derive(Deserialize, Serialize, Clone)]
18enum LeaseStatus {
19 Offered,
20 Acked,
21}
22
23#[derive(Deserialize, Serialize, Clone)]
24struct Lease {
25 ip: Ipv4Addr,
26 status: LeaseStatus,
27 is_static: bool,
28 when: SystemTime,
30}
31
32impl Lease {
33 #[inline]
34 fn new(ip: Ipv4Addr, status: LeaseStatus, is_static: bool) -> Self {
35 Self {
36 ip,
37 status,
38 is_static,
39 when: SystemTime::now(),
40 }
41 }
42
43 #[inline]
45 fn extend(&mut self) {
46 self.when = SystemTime::now();
47 }
48
49 fn is_expired(&self, lease_time: u32) -> Result<bool> {
50 match self.when.elapsed() {
51 Ok(elapsed) => Ok(elapsed >= Duration::from_secs(lease_time as u64)),
52 Err(_) => Err("Problem getting elapsed system time"),
53 }
54 }
55}
56
57#[derive(Deserialize, Serialize)]
59pub struct Leases {
60 #[serde(skip)]
61 available: HashSet<Ipv4Addr>,
62 leased: HashMap<MacAddress, Lease>,
63 #[serde(skip)]
66 static_leases: HashMap<MacAddress, Ipv4Addr>,
67 lease_time: u32,
69 #[serde(skip)]
74 leases_file: Option<File>,
75}
76
77impl Leases {
78 pub const FILE_NAME: &'static str = "toe-beans-leases.toml";
80
81 const OFFER_TIMEOUT: u32 = 300;
88
89 fn open_leases_file(path: &PathBuf) -> File {
90 OpenOptions::new()
91 .read(false)
92 .write(true)
93 .create(true)
94 .truncate(true)
95 .mode(0o644) .open(path.join(Self::FILE_NAME))
97 .expect("Failed to open leases file")
98 }
99
100 fn fill_available(&mut self, config: &Config) {
102 let network = config.network_cidr;
103 let hosts = network.hosts();
104
105 self.available.extend(hosts);
106
107 self.available.remove(&network.broadcast_address());
109 self.available.remove(&network.network_address());
110
111 self.leased.values().for_each(|lease| {
113 self.available.remove(&lease.ip);
114 });
115
116 self.static_leases.values().for_each(|ip| {
118 self.available.remove(ip);
119 });
120 }
121
122 pub fn new(config: &Config) -> Self {
124 #[cfg(not(feature = "benchmark"))]
125 let leases_file = Some(Self::open_leases_file(&config.path));
126 #[cfg(feature = "benchmark")]
127 let leases_file = None;
128
129 let size = config.network_cidr.hosts().len();
130
131 let mut leases = Self {
132 available: HashSet::with_capacity(size),
133 leased: HashMap::with_capacity(size),
134 static_leases: config.static_leases.clone(),
135 lease_time: config.lease_time.as_server(),
136 leases_file,
137 };
138
139 leases.fill_available(config);
140
141 leases
142 }
143
144 #[inline]
148 fn validate(&self, config: &Config) -> Result<()> {
149 let network = config.network_cidr;
150 let broadcast_address = network.broadcast_address();
151 let network_address = network.network_address();
152
153 self.static_leases.values().try_for_each(|static_ip| {
154 if !network.contains(*static_ip) {
155 return Err("The configured network range does not include the restored static ip. Did the network range change?");
156 }
157
158 if static_ip == &broadcast_address {
159 return Err("The network's broadcast address can't be statically leased");
160 }
161
162 if static_ip == &network_address {
163 return Err("The network's network address can't be statically leased");
164 }
165
166 Ok(())
167 })?;
168
169 self.leased.values().try_for_each(|lease| {
170 let ip = lease.ip;
171
172 if !network.contains(ip) {
173 return Err("The configured network range does not include the restored leased ip. Did the network range change?");
174 }
175
176 if ip == broadcast_address {
177 return Err("The network's broadcast address can't be leased");
178 }
179
180 if ip == network_address {
181 return Err("The network's network address can't be leased");
182 }
183
184 Ok(())
185 })?;
186
187 Ok(())
188 }
189
190 pub fn restore(config: &Config) -> Result<Self> {
193 let read_result = read_to_string(config.path.join(Self::FILE_NAME));
194
195 let mut leases: Leases = match read_result {
196 Ok(toml_string) => {
197 let toml_result = toml::from_str(&toml_string);
198 match toml_result {
199 Ok(leases) => leases,
200 Err(_) => return Err("Problem parsing leases file"),
201 }
202 }
203 Err(_) => {
204 return Err("Problem reading leases file");
205 }
206 };
207
208 leases.validate(config)?;
209
210 leases.leases_file = Some(Self::open_leases_file(&config.path));
211 leases.static_leases = config.static_leases.clone();
212 leases.fill_available(config);
213
214 info!("Restored IP address leases");
215 Ok(leases)
216 }
217
218 pub fn restore_or_new(config: &Config) -> Self {
221 Leases::restore(config).unwrap_or_else(|_| Leases::new(config))
222 }
223
224 fn commit(&mut self) -> Result<()> {
226 debug!("Writing {}", Self::FILE_NAME);
227
228 let file_content = match toml::to_string_pretty(&self) {
229 Ok(content) => content,
230 Err(_) => return Err("Failed to generate toml content for leases file"),
231 };
232
233 match self
234 .leases_file
235 .as_mut()
236 .expect("A leases file should always be Some at this point")
237 .write_all(file_content.as_bytes())
238 {
239 Ok(_) => Ok(()),
240 Err(_) => Err("Failed to write to leases file"),
241 }
242 }
243
244 pub fn offer(&mut self, owner: MacAddress, requested_ip: Option<Ipv4Addr>) -> Result<Ipv4Addr> {
254 if let Some(lease) = self.leased.get_mut(&owner) {
255 match lease.status {
256 LeaseStatus::Offered => {
257 warn!(
258 "{} will be offered an IP address that it has already been offered",
259 owner
260 );
261 lease.extend();
262 return Ok(lease.ip);
263 }
264 LeaseStatus::Acked => {
265 if !lease.is_expired(self.lease_time)? {
266 warn!(
267 "{} will be offered its non-expired, acked IP address again",
268 owner
269 );
270 lease.status = LeaseStatus::Offered;
271 lease.extend();
272 return Ok(lease.ip);
273 }
274 }
276 }
277 }
278
279 if let Some(requested_ip) = requested_ip {
280 let not_available = !self.available.contains(&requested_ip);
281 let has_static_lease = self.static_leases.contains_key(&owner);
282
283 if not_available {
284 debug!("Requested IP Address is not available (or maybe not in network range)");
286 } else if has_static_lease {
287 debug!("Requested IP Address ignored because owner has static lease");
288 } else {
289 self.available.remove(&requested_ip);
290 self.leased
291 .insert(owner, Lease::new(requested_ip, LeaseStatus::Offered, false));
292 return Ok(requested_ip);
293 }
294 }
295
296 let lease = self.get_lease(&owner, LeaseStatus::Offered)?;
298 let ip = lease.ip;
299 self.leased.insert(owner, lease);
300 Ok(ip)
301 }
302
303 pub fn ack(&mut self, owner: MacAddress) -> Result<Ipv4Addr> {
306 let maybe_leased = self.leased.get_mut(&owner);
307 let ip = match maybe_leased {
308 Some(leased) => {
309 match leased.status {
310 LeaseStatus::Offered => {
311 leased.status = LeaseStatus::Acked;
312 }
313 LeaseStatus::Acked => {
314 warn!(
315 "{} will be leased an IP address it was already leased",
316 owner
317 );
318 }
319 };
320
321 leased.extend();
322 leased.ip
323 }
324 None => {
325 let lease = self.get_lease(&owner, LeaseStatus::Acked)?;
327 let ip = lease.ip;
328 self.leased.insert(owner, lease);
329 ip
330 }
331 };
332
333 #[cfg(not(feature = "benchmark"))]
334 self.commit()?;
335
336 Ok(ip)
337 }
338
339 pub fn extend(&mut self, owner: MacAddress) -> Result<()> {
342 match self.leased.get_mut(&owner) {
343 Some(lease) => {
344 lease.extend();
345 Ok(())
346 }
347 None => Err("No IP address lease found with that owner"),
348 }
349 }
350
351 pub fn release(&mut self, owner: MacAddress) -> Result<()> {
353 let maybe_leased = self.leased.remove(&owner);
354
355 match maybe_leased {
356 Some(lease) => {
357 if !lease.is_static {
358 self.available.insert(lease.ip);
359 }
360 Ok(())
361 }
362 None => Err("No IP address lease found with that owner"),
363 }
364 }
365
366 fn get_lease(&mut self, owner: &MacAddress, status: LeaseStatus) -> Result<Lease> {
368 let lease = match self.static_leases.get(owner) {
369 Some(ip) => Lease::new(*ip, status, true),
370 None => Lease::new(self.get_ip()?, status, false),
371 };
372 Ok(lease)
373 }
374
375 fn get_ip(&mut self) -> Result<Ipv4Addr> {
380 if let Some(any) = self.available.iter().next().cloned() {
381 debug!("Chose available IP address {any}");
382 return Ok(self.available.take(&any).unwrap());
384 }
385
386 let maybe_expired_lease = self
387 .leased
388 .clone() .into_iter() .find(|(_owner, lease)| match lease.status {
391 LeaseStatus::Offered => lease.is_expired(Self::OFFER_TIMEOUT).unwrap_or(false),
392 LeaseStatus::Acked => lease.is_expired(self.lease_time).unwrap_or(false),
393 });
394
395 if let Some(expired_lease) = maybe_expired_lease {
396 debug!("Reusing expired lease's IP address");
397 return Ok(self.leased.remove(&expired_lease.0).unwrap().ip);
399 }
400
401 Err("No more IP addresses available")
402 }
403
404 pub fn is_available(&self, ip: &Ipv4Addr) -> bool {
406 self.available.contains(ip)
407 }
408
409 pub fn verify_lease(&self, owner: MacAddress, ip: &Ipv4Addr) -> Result<()> {
412 let maybe_leased = self.leased.get(&owner);
413
414 match maybe_leased {
415 Some(lease) => {
416 if let LeaseStatus::Offered = lease.status {
417 return Err("Lease offered but not previously acked");
418 }
419
420 if &lease.ip != ip {
421 return Err("Client's notion of ip address is wrong");
422 }
423
424 if lease.is_expired(self.lease_time)? {
425 return Err("Lease has expired");
426 }
427
428 Ok(())
429 }
430 None => Err("No IP address lease found with that owner"),
431 }
432 }
433}