veridian_kernel/services/cni/
ipam.rs1#![allow(dead_code)]
7
8use alloc::{string::String, vec::Vec};
9
10#[derive(Debug, Clone)]
16pub struct IpamConfig {
17 pub subnet: u32,
19 pub prefix_len: u8,
21 pub gateway: u32,
23 pub range_start: u32,
25 pub range_end: u32,
27}
28
29impl IpamConfig {
30 pub fn new(subnet: u32, prefix_len: u8, gateway_host: u32) -> Self {
36 let mask = if prefix_len >= 32 {
37 0xFFFF_FFFF
38 } else {
39 !((1u32 << (32 - prefix_len)) - 1)
40 };
41 let subnet_base = subnet & mask;
42 let host_bits = 32 - prefix_len as u32;
43 let max_host = if host_bits >= 32 {
44 0xFFFF_FFFF
45 } else {
46 (1u32 << host_bits) - 1
47 };
48
49 IpamConfig {
50 subnet: subnet_base,
51 prefix_len,
52 gateway: subnet_base | gateway_host,
53 range_start: 2, range_end: max_host.saturating_sub(1), }
56 }
57
58 pub fn mask(&self) -> u32 {
60 if self.prefix_len >= 32 {
61 0xFFFF_FFFF
62 } else {
63 !((1u32 << (32 - self.prefix_len)) - 1)
64 }
65 }
66
67 pub fn total_addresses(&self) -> u32 {
69 if self.range_end >= self.range_start {
70 self.range_end - self.range_start + 1
71 } else {
72 0
73 }
74 }
75
76 pub fn format_ip(ip: u32) -> String {
78 alloc::format!(
79 "{}.{}.{}.{}",
80 (ip >> 24) & 0xFF,
81 (ip >> 16) & 0xFF,
82 (ip >> 8) & 0xFF,
83 ip & 0xFF
84 )
85 }
86}
87
88#[derive(Debug, Clone, PartialEq, Eq)]
94pub enum IpamError {
95 Exhausted,
97 NotAllocated(u32),
99 AlreadyAllocated(u32),
101 OutOfRange(u32),
103}
104
105#[derive(Debug)]
111pub struct IpamAllocator {
112 bitmap: Vec<u64>,
114 subnet_base: u32,
116 prefix_len: u8,
118 range_start: u32,
120 range_end: u32,
122 allocated: u32,
124 gateway: u32,
126}
127
128impl IpamAllocator {
129 pub fn new(config: &IpamConfig) -> Self {
131 let total = config.total_addresses();
133 let bitmap_len = (total as usize).div_ceil(64);
134
135 IpamAllocator {
136 bitmap: alloc::vec![0u64; bitmap_len],
137 subnet_base: config.subnet,
138 prefix_len: config.prefix_len,
139 range_start: config.range_start,
140 range_end: config.range_end,
141 allocated: 0,
142 gateway: config.gateway,
143 }
144 }
145
146 pub fn allocate(&mut self) -> Result<u32, IpamError> {
150 let total = self.total_addresses();
151 if self.allocated >= total {
152 return Err(IpamError::Exhausted);
153 }
154
155 for (word_idx, word) in self.bitmap.iter_mut().enumerate() {
157 if *word == u64::MAX {
158 continue;
159 }
160 let bit = (!*word).trailing_zeros();
162 if bit >= 64 {
163 continue;
164 }
165 let offset = (word_idx as u32) * 64 + bit;
166 let host_part = self.range_start + offset;
167 if host_part > self.range_end {
168 break;
169 }
170
171 *word |= 1u64 << bit;
172 self.allocated += 1;
173 return Ok(self.subnet_base | host_part);
174 }
175
176 Err(IpamError::Exhausted)
177 }
178
179 pub fn release(&mut self, ip: u32) -> Result<(), IpamError> {
181 let host_part = ip & !self.mask();
182
183 if host_part < self.range_start || host_part > self.range_end {
184 return Err(IpamError::OutOfRange(ip));
185 }
186
187 let offset = host_part - self.range_start;
188 let word_idx = (offset / 64) as usize;
189 let bit = offset % 64;
190
191 if word_idx >= self.bitmap.len() {
192 return Err(IpamError::OutOfRange(ip));
193 }
194
195 if self.bitmap[word_idx] & (1u64 << bit) == 0 {
196 return Err(IpamError::NotAllocated(ip));
197 }
198
199 self.bitmap[word_idx] &= !(1u64 << bit);
200 self.allocated = self.allocated.saturating_sub(1);
201 Ok(())
202 }
203
204 pub fn is_allocated(&self, ip: u32) -> bool {
206 let host_part = ip & !self.mask();
207 if host_part < self.range_start || host_part > self.range_end {
208 return false;
209 }
210 let offset = host_part - self.range_start;
211 let word_idx = (offset / 64) as usize;
212 let bit = offset % 64;
213 if word_idx >= self.bitmap.len() {
214 return false;
215 }
216 self.bitmap[word_idx] & (1u64 << bit) != 0
217 }
218
219 pub fn available_count(&self) -> u32 {
221 self.total_addresses().saturating_sub(self.allocated)
222 }
223
224 pub fn allocated_count(&self) -> u32 {
226 self.allocated
227 }
228
229 pub fn total_addresses(&self) -> u32 {
231 if self.range_end >= self.range_start {
232 self.range_end - self.range_start + 1
233 } else {
234 0
235 }
236 }
237
238 fn mask(&self) -> u32 {
240 if self.prefix_len >= 32 {
241 0xFFFF_FFFF
242 } else {
243 !((1u32 << (32 - self.prefix_len)) - 1)
244 }
245 }
246
247 pub fn gateway(&self) -> u32 {
249 self.gateway
250 }
251
252 pub fn status_string(&self) -> String {
254 alloc::format!(
255 "subnet={}/{} allocated={}/{} available={}",
256 IpamConfig::format_ip(self.subnet_base),
257 self.prefix_len,
258 self.allocated,
259 self.total_addresses(),
260 self.available_count()
261 )
262 }
263}
264
265#[cfg(test)]
270mod tests {
271 use super::*;
272
273 fn ip(a: u8, b: u8, c: u8, d: u8) -> u32 {
274 ((a as u32) << 24) | ((b as u32) << 16) | ((c as u32) << 8) | (d as u32)
275 }
276
277 fn make_allocator() -> IpamAllocator {
278 let config = IpamConfig::new(ip(10, 244, 0, 0), 24, 1);
279 IpamAllocator::new(&config)
280 }
281
282 #[test]
283 fn test_ipam_config_mask() {
284 let config = IpamConfig::new(ip(10, 244, 0, 0), 24, 1);
285 assert_eq!(config.mask(), 0xFFFFFF00);
286 assert_eq!(config.gateway, ip(10, 244, 0, 1));
287 }
288
289 #[test]
290 fn test_ipam_config_total_addresses() {
291 let config = IpamConfig::new(ip(10, 244, 0, 0), 24, 1);
292 assert_eq!(config.total_addresses(), 253);
294 }
295
296 #[test]
297 fn test_allocate_first() {
298 let mut alloc = make_allocator();
299 let addr = alloc.allocate().unwrap();
300 assert_eq!(addr, ip(10, 244, 0, 2));
301 }
302
303 #[test]
304 fn test_allocate_sequential() {
305 let mut alloc = make_allocator();
306 let a1 = alloc.allocate().unwrap();
307 let a2 = alloc.allocate().unwrap();
308 let a3 = alloc.allocate().unwrap();
309 assert_eq!(a1, ip(10, 244, 0, 2));
310 assert_eq!(a2, ip(10, 244, 0, 3));
311 assert_eq!(a3, ip(10, 244, 0, 4));
312 assert_eq!(alloc.allocated_count(), 3);
313 }
314
315 #[test]
316 fn test_release_and_realloc() {
317 let mut alloc = make_allocator();
318 let a1 = alloc.allocate().unwrap();
319 let _a2 = alloc.allocate().unwrap();
320 alloc.release(a1).unwrap();
321 assert_eq!(alloc.allocated_count(), 1);
322
323 let a3 = alloc.allocate().unwrap();
325 assert_eq!(a3, a1);
326 }
327
328 #[test]
329 fn test_is_allocated() {
330 let mut alloc = make_allocator();
331 let addr = alloc.allocate().unwrap();
332 assert!(alloc.is_allocated(addr));
333 alloc.release(addr).unwrap();
334 assert!(!alloc.is_allocated(addr));
335 }
336
337 #[test]
338 fn test_release_not_allocated() {
339 let mut alloc = make_allocator();
340 assert_eq!(
341 alloc.release(ip(10, 244, 0, 50)),
342 Err(IpamError::NotAllocated(ip(10, 244, 0, 50)))
343 );
344 }
345
346 #[test]
347 fn test_release_out_of_range() {
348 let mut alloc = make_allocator();
349 assert_eq!(
350 alloc.release(ip(192, 168, 0, 1)),
351 Err(IpamError::OutOfRange(ip(192, 168, 0, 1)))
352 );
353 }
354
355 #[test]
356 fn test_format_ip() {
357 assert_eq!(IpamConfig::format_ip(ip(10, 244, 0, 1)), "10.244.0.1");
358 assert_eq!(IpamConfig::format_ip(ip(192, 168, 1, 100)), "192.168.1.100");
359 }
360}