zlayer_overlay/
allocator.rs1use crate::error::{OverlayError, Result};
6use ipnet::Ipv4Net;
7use serde::{Deserialize, Serialize};
8use std::collections::HashSet;
9use std::net::Ipv4Addr;
10use std::path::Path;
11
12#[derive(Debug, Clone)]
17pub struct IpAllocator {
18 network: Ipv4Net,
20 allocated: HashSet<Ipv4Addr>,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct IpAllocatorState {
27 pub cidr: String,
29 pub allocated: Vec<Ipv4Addr>,
31}
32
33impl IpAllocator {
34 pub fn new(cidr: &str) -> Result<Self> {
50 let network: Ipv4Net = cidr
51 .parse()
52 .map_err(|e| OverlayError::InvalidCidr(format!("{cidr}: {e}")))?;
53
54 Ok(Self {
55 network,
56 allocated: HashSet::new(),
57 })
58 }
59
60 pub fn from_state(state: IpAllocatorState) -> Result<Self> {
66 let mut allocator = Self::new(&state.cidr)?;
67 for ip in state.allocated {
68 allocator.mark_allocated(ip)?;
69 }
70 Ok(allocator)
71 }
72
73 #[must_use]
75 pub fn to_state(&self) -> IpAllocatorState {
76 IpAllocatorState {
77 cidr: self.network.to_string(),
78 allocated: self.allocated.iter().copied().collect(),
79 }
80 }
81
82 pub async fn load(path: &Path) -> Result<Self> {
88 let contents = tokio::fs::read_to_string(path).await?;
89 let state: IpAllocatorState = serde_json::from_str(&contents)?;
90 Self::from_state(state)
91 }
92
93 pub async fn save(&self, path: &Path) -> Result<()> {
99 let state = self.to_state();
100 let contents = serde_json::to_string_pretty(&state)?;
101 tokio::fs::write(path, contents).await?;
102 Ok(())
103 }
104
105 pub fn allocate(&mut self) -> Option<Ipv4Addr> {
118 for ip in self.network.hosts() {
120 if !self.allocated.contains(&ip) {
121 self.allocated.insert(ip);
122 return Some(ip);
123 }
124 }
125 None
126 }
127
128 pub fn allocate_specific(&mut self, ip: Ipv4Addr) -> Result<()> {
134 if !self.network.contains(&ip) {
135 return Err(OverlayError::IpNotInRange(ip, self.network.to_string()));
136 }
137
138 if self.allocated.contains(&ip) {
139 return Err(OverlayError::IpAlreadyAllocated(ip));
140 }
141
142 self.allocated.insert(ip);
143 Ok(())
144 }
145
146 pub fn allocate_first(&mut self) -> Result<Ipv4Addr> {
161 let first_ip = self
162 .network
163 .hosts()
164 .next()
165 .ok_or(OverlayError::NoAvailableIps)?;
166
167 if self.allocated.contains(&first_ip) {
168 return Err(OverlayError::IpAlreadyAllocated(first_ip));
169 }
170
171 self.allocated.insert(first_ip);
172 Ok(first_ip)
173 }
174
175 pub fn mark_allocated(&mut self, ip: Ipv4Addr) -> Result<()> {
181 if !self.network.contains(&ip) {
182 return Err(OverlayError::IpNotInRange(ip, self.network.to_string()));
183 }
184 self.allocated.insert(ip);
185 Ok(())
186 }
187
188 pub fn release(&mut self, ip: Ipv4Addr) -> bool {
192 self.allocated.remove(&ip)
193 }
194
195 #[must_use]
197 pub fn is_allocated(&self, ip: Ipv4Addr) -> bool {
198 self.allocated.contains(&ip)
199 }
200
201 #[must_use]
203 pub fn contains(&self, ip: Ipv4Addr) -> bool {
204 self.network.contains(&ip)
205 }
206
207 #[must_use]
209 pub fn allocated_count(&self) -> usize {
210 self.allocated.len()
211 }
212
213 #[must_use]
215 #[allow(clippy::cast_possible_truncation)]
216 pub fn total_hosts(&self) -> u32 {
217 self.network.hosts().count() as u32
218 }
219
220 #[must_use]
222 #[allow(clippy::cast_possible_truncation)]
223 pub fn available_count(&self) -> u32 {
224 self.total_hosts()
225 .saturating_sub(self.allocated.len() as u32)
226 }
227
228 #[must_use]
230 pub fn cidr(&self) -> String {
231 self.network.to_string()
232 }
233
234 #[must_use]
236 pub fn network_addr(&self) -> Ipv4Addr {
237 self.network.network()
238 }
239
240 #[must_use]
242 pub fn broadcast_addr(&self) -> Ipv4Addr {
243 self.network.broadcast()
244 }
245
246 #[must_use]
248 pub fn prefix_len(&self) -> u8 {
249 self.network.prefix_len()
250 }
251
252 #[must_use]
254 pub fn allocated_ips(&self) -> Vec<Ipv4Addr> {
255 self.allocated.iter().copied().collect()
256 }
257}
258
259pub fn first_ip_from_cidr(cidr: &str) -> Result<Ipv4Addr> {
265 let network: Ipv4Net = cidr
266 .parse()
267 .map_err(|e| OverlayError::InvalidCidr(format!("{cidr}: {e}")))?;
268
269 network.hosts().next().ok_or(OverlayError::NoAvailableIps)
270}
271
272#[cfg(test)]
273mod tests {
274 use super::*;
275
276 #[test]
277 fn test_allocator_new() {
278 let allocator = IpAllocator::new("10.200.0.0/24").unwrap();
279 assert_eq!(allocator.cidr(), "10.200.0.0/24");
280 assert_eq!(allocator.allocated_count(), 0);
281 }
282
283 #[test]
284 fn test_allocator_invalid_cidr() {
285 let result = IpAllocator::new("invalid");
286 assert!(result.is_err());
287 }
288
289 #[test]
290 fn test_allocate_sequential() {
291 let mut allocator = IpAllocator::new("10.200.0.0/30").unwrap();
292
293 let ip1 = allocator.allocate().unwrap();
295 let ip2 = allocator.allocate().unwrap();
296
297 assert_eq!(ip1.to_string(), "10.200.0.1");
298 assert_eq!(ip2.to_string(), "10.200.0.2");
299
300 assert!(allocator.allocate().is_none());
302 }
303
304 #[test]
305 fn test_allocate_first() {
306 let mut allocator = IpAllocator::new("10.200.0.0/24").unwrap();
307
308 let first = allocator.allocate_first().unwrap();
309 assert_eq!(first.to_string(), "10.200.0.1");
310
311 assert!(allocator.allocate_first().is_err());
313 }
314
315 #[test]
316 fn test_allocate_specific() {
317 let mut allocator = IpAllocator::new("10.200.0.0/24").unwrap();
318
319 let specific_ip: Ipv4Addr = "10.200.0.50".parse().unwrap();
320 allocator.allocate_specific(specific_ip).unwrap();
321
322 assert!(allocator.is_allocated(specific_ip));
323
324 assert!(allocator.allocate_specific(specific_ip).is_err());
326 }
327
328 #[test]
329 fn test_allocate_specific_out_of_range() {
330 let mut allocator = IpAllocator::new("10.200.0.0/24").unwrap();
331
332 let out_of_range: Ipv4Addr = "192.168.1.1".parse().unwrap();
333 assert!(allocator.allocate_specific(out_of_range).is_err());
334 }
335
336 #[test]
337 fn test_release() {
338 let mut allocator = IpAllocator::new("10.200.0.0/24").unwrap();
339
340 let ip = allocator.allocate().unwrap();
341 assert!(allocator.is_allocated(ip));
342
343 assert!(allocator.release(ip));
344 assert!(!allocator.is_allocated(ip));
345
346 let ip2 = allocator.allocate().unwrap();
348 assert_eq!(ip, ip2);
349 }
350
351 #[test]
352 fn test_mark_allocated() {
353 let mut allocator = IpAllocator::new("10.200.0.0/24").unwrap();
354
355 let ip: Ipv4Addr = "10.200.0.100".parse().unwrap();
356 allocator.mark_allocated(ip).unwrap();
357
358 assert!(allocator.is_allocated(ip));
359 }
360
361 #[test]
362 fn test_contains() {
363 let allocator = IpAllocator::new("10.200.0.0/24").unwrap();
364
365 assert!(allocator.contains("10.200.0.50".parse().unwrap()));
366 assert!(!allocator.contains("10.201.0.50".parse().unwrap()));
367 }
368
369 #[test]
370 fn test_total_hosts() {
371 let allocator = IpAllocator::new("10.200.0.0/24").unwrap();
373 assert_eq!(allocator.total_hosts(), 254);
374
375 let allocator = IpAllocator::new("10.200.0.0/30").unwrap();
377 assert_eq!(allocator.total_hosts(), 2);
378 }
379
380 #[test]
381 fn test_available_count() {
382 let mut allocator = IpAllocator::new("10.200.0.0/30").unwrap();
383
384 assert_eq!(allocator.available_count(), 2);
385
386 allocator.allocate();
387 assert_eq!(allocator.available_count(), 1);
388
389 allocator.allocate();
390 assert_eq!(allocator.available_count(), 0);
391 }
392
393 #[test]
394 fn test_state_roundtrip() {
395 let mut allocator = IpAllocator::new("10.200.0.0/24").unwrap();
396 allocator.allocate();
397 allocator.allocate();
398
399 let state = allocator.to_state();
400 let restored = IpAllocator::from_state(state).unwrap();
401
402 assert_eq!(allocator.cidr(), restored.cidr());
403 assert_eq!(allocator.allocated_count(), restored.allocated_count());
404 }
405
406 #[test]
407 fn test_first_ip_from_cidr() {
408 let ip = first_ip_from_cidr("10.200.0.0/24").unwrap();
409 assert_eq!(ip.to_string(), "10.200.0.1");
410 }
411}