scion_sdk_address_manager/
allocator.rs

1// Copyright 2025 Anapaya Systems
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//   http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//! An IP address allocator.
15
16use std::net::IpAddr;
17
18use ipnet::IpNet;
19use rand::Rng as _;
20use rand_chacha::ChaCha8Rng;
21use thiserror::Error;
22
23use crate::allocator::addr_set::{AddrSet, AddrSetError};
24
25mod addr_set;
26pub(crate) mod dto;
27
28/// An allocator of IP addresses.
29// Impl Note:
30// The AddrSets contain all free addresses.
31// On Allocation - Address is removed from the Set
32// On Free       - Address is added to the Set.
33#[derive(Debug, Eq, PartialEq, Clone)]
34pub struct AddressAllocator {
35    /// Non overlapping ranges sorted by start address.
36    v4: Vec<AddrSet>,
37    /// Non overlapping ranges sorted by start address.
38    v6: Vec<AddrSet>,
39    /// RNG for random address allocation.
40    rng: ChaCha8Rng,
41}
42
43/// Address allocator creation errors.
44#[derive(Debug, Error)]
45pub enum AllocatorCreationError {
46    /// Prefixes are too large.
47    #[error("prefixes containing more than 2^128 addresses are not supported")]
48    PrefixesTooLarge,
49}
50
51impl AddressAllocator {
52    /// Creates a new AddressAllocator.
53    ///
54    /// # Arguments
55    /// * `prefixes` - A list of non-overlapping prefixes to allocate from.
56    /// * `rng` - A random number generator for random address allocation.
57    pub fn new(prefixes: Vec<IpNet>, rng: ChaCha8Rng) -> Result<Self, AllocatorCreationError> {
58        // Sort prefixes by network address.
59        let mut prefixes = prefixes;
60        prefixes.sort_by_key(|prefix| prefix.network());
61        // assert: aggregated prefixes are non-overlapping
62        let aggregated = IpNet::aggregate(&prefixes);
63        // If the sum of all prefixes contain more than 2^128 addresses, return an error.
64        if aggregated
65            .iter()
66            .map(|p| 2u128.pow(p.prefix_len() as u32))
67            .try_fold(0_u128, |acc, x| acc.checked_add(x))
68            .is_none()
69        {
70            return Err(AllocatorCreationError::PrefixesTooLarge);
71        }
72
73        let mut v4 = Vec::new();
74        let mut v6 = Vec::new();
75
76        for prefix in aggregated {
77            match prefix.network() {
78                IpAddr::V4(_) => v4.push(AddrSet::new(prefix)),
79                IpAddr::V6(_) => v6.push(AddrSet::new(prefix)),
80            }
81        }
82
83        Ok(Self { v4, v6, rng })
84    }
85
86    /// Returns a mutable reference to the address set containing the given address.
87    fn mut_address_set(&mut self, address: &IpAddr) -> Result<&mut AddrSet, AddressAllocatorError> {
88        let res = match address {
89            IpAddr::V4(_) => self.v4.iter_mut().find(|set| set.prefix.contains(address)),
90            IpAddr::V6(_) => self.v6.iter_mut().find(|set| set.prefix.contains(address)),
91        };
92
93        res.ok_or(AddressAllocatorError::AddressNotInPrefix(*address))
94    }
95
96    /// Frees a certain Address
97    ///
98    /// Returns an error if the address is not in in Prefix
99    pub fn free(&mut self, address: IpAddr) -> Result<(), AddressAllocatorError> {
100        self.mut_address_set(&address)?.insert(address)?;
101        Ok(())
102    }
103
104    /// Allocates a specific address
105    ///
106    /// Returns an error if the address is already allocated, or not in in Prefix
107    fn allocate_specific(&mut self, address: IpAddr) -> Result<(), AddressAllocatorError> {
108        self.mut_address_set(&address)?.remove(address)?;
109        Ok(())
110    }
111
112    /// Check if an address is free
113    pub fn is_free(&self, address: IpAddr) -> bool {
114        match address {
115            IpAddr::V4(_) => self.v4.iter().any(|set| set.contains(address)),
116            IpAddr::V6(_) => self.v6.iter().any(|set| set.contains(address)),
117        }
118    }
119
120    /// Allocate an address from the address allocator. If the requested address is unspecified, a
121    /// random address of the given family is allocated.
122    pub fn allocate(&mut self, requested: IpAddr) -> Result<IpAddr, AddressAllocatorError> {
123        if requested.is_unspecified() {
124            match requested {
125                IpAddr::V4(_) => {
126                    if self.free_v4() == 0 {
127                        return Err(AddressAllocatorError::NoAddressesAvailable);
128                    }
129                    let mut n = self.rng.random_range(0..self.free_v4());
130                    for set in self.v4.iter_mut() {
131                        if n < set.len() {
132                            let addr = set.nth_free(n).expect("Checked above");
133                            self.allocate_specific(addr)?;
134                            return Ok(addr);
135                        }
136                        n -= set.len();
137                    }
138                }
139                IpAddr::V6(_) => {
140                    if self.free_v6() == 0 {
141                        return Err(AddressAllocatorError::NoAddressesAvailable);
142                    }
143                    let mut n = self.rng.random_range(0..self.free_v6());
144                    for set in self.v6.iter_mut() {
145                        if n < set.len() {
146                            let addr = set.nth_free(n).expect("Checked above");
147                            self.allocate_specific(addr)?;
148                            return Ok(addr);
149                        }
150                        n -= set.len();
151                    }
152                }
153            }
154            Err(AddressAllocatorError::NoAddressesAvailable)
155        } else {
156            self.allocate_specific(requested)?;
157            Ok(requested)
158        }
159    }
160
161    pub(crate) fn free_v4(&self) -> u128 {
162        self.v4.iter().map(|set| set.len()).sum::<u128>()
163    }
164
165    pub(crate) fn free_v6(&self) -> u128 {
166        self.v6.iter().map(|set| set.len()).sum::<u128>()
167    }
168}
169
170/// Address allocation errors.
171#[derive(Debug, Error, PartialEq, Eq)]
172pub enum AddressAllocatorError {
173    /// Address not in any allocation prefix.
174    #[error("requested address {0} not in allocation prefixes")]
175    AddressNotInPrefix(IpAddr),
176    /// Address already allocated.
177    #[error("address {0} already allocated")]
178    AddressAlreadyAllocated(IpAddr),
179    /// Address is already free.
180    #[error("address is already freed")]
181    AddressAlreadyFreed(IpAddr),
182    /// All addresses are allocated.
183    #[error("no addresses available")]
184    NoAddressesAvailable,
185}
186
187impl From<AddrSetError> for AddressAllocatorError {
188    fn from(err: AddrSetError) -> Self {
189        match err {
190            AddrSetError::AddressNotInPrefix(addr) => {
191                AddressAllocatorError::AddressNotInPrefix(addr)
192            }
193            AddrSetError::AddressAlreadyInSet(addr) => {
194                AddressAllocatorError::AddressAlreadyFreed(addr)
195            }
196            AddrSetError::AddressNotInSet(addr) => {
197                AddressAllocatorError::AddressAlreadyAllocated(addr)
198            }
199            // This should never happen.
200            AddrSetError::WrongIPVersion(_addr) => {
201                unreachable!("Allocator always supports both IPv4 and IPv6")
202            }
203        }
204    }
205}
206
207#[cfg(test)]
208mod tests {
209    use std::{
210        net::{IpAddr, Ipv4Addr, Ipv6Addr},
211        str::FromStr,
212    };
213
214    use rand::SeedableRng;
215
216    use super::*;
217
218    // Function to check invariants
219    fn check_allocator_invariants(allocator: &AddressAllocator) {
220        // Check that intervals are disjoint and ordered
221        for sets in [&allocator.v4, &allocator.v6] {
222            for set in sets {
223                for i in 1..set.free.ranges().len() {
224                    // Intervals should be ordered
225                    assert!(
226                        set.free.ranges()[i - 1].start < set.free.ranges()[i].start,
227                        "{:?} Interval not ordered: {:?} and {:?}",
228                        set.prefix,
229                        set.free.ranges()[i - 1],
230                        set.free.ranges()[i]
231                    );
232
233                    // Intervals should be disjoint
234                    assert!(
235                        set.free.ranges()[i - 1].end < set.free.ranges()[i].start,
236                        "{:?} Intervals not disjoint: {:?} and {:?}",
237                        set.prefix,
238                        set.free.ranges()[i - 1],
239                        set.free.ranges()[i]
240                    );
241                }
242
243                // Check that each interval is valid (start < end)
244                for interval in set.free.ranges() {
245                    assert!(
246                        interval.start < interval.end,
247                        "{:?} Invalid interval: {:?}",
248                        set.prefix,
249                        interval
250                    );
251                }
252            }
253        }
254    }
255
256    #[test]
257    fn test_allocate_random() {
258        let prefixes = vec![
259            "192.168.0.0/24".parse().unwrap(),
260            "10.0.0.0/16".parse().unwrap(),
261            "2001:db8::/64".parse().unwrap(),
262        ];
263
264        let mut expected_length = 2u128.pow(8) + 2u128.pow(16) + 2u128.pow(64);
265
266        let mut allocator = AddressAllocator::new(prefixes, ChaCha8Rng::seed_from_u64(42)).unwrap();
267        let mut allocated = Vec::new();
268        // IPv4 addresses
269        for _ in 0..1000 {
270            let addr = allocator
271                .allocate(IpAddr::V4(Ipv4Addr::UNSPECIFIED))
272                .expect("Failed to allocate");
273            check_allocator_invariants(&allocator);
274            assert!(!allocator.is_free(addr));
275            assert!(addr.is_ipv4());
276            expected_length -= 1;
277            assert_eq!(allocator.free_v4() + allocator.free_v6(), expected_length);
278            allocated.push(addr);
279        }
280        // IPv6 addresses
281        for _ in 0..1000 {
282            let addr = allocator
283                .allocate(IpAddr::V6(Ipv6Addr::UNSPECIFIED))
284                .expect("Failed to allocate");
285            check_allocator_invariants(&allocator);
286            assert!(!allocator.is_free(addr));
287            assert!(addr.is_ipv6());
288            expected_length -= 1;
289            assert_eq!(allocator.free_v4() + allocator.free_v6(), expected_length);
290            allocated.push(addr);
291        }
292        // Insert the addresses back into the allocator
293        for addr in allocated {
294            allocator.free(addr).unwrap();
295            check_allocator_invariants(&allocator);
296            expected_length += 1;
297            assert_eq!(allocator.free_v4() + allocator.free_v6(), expected_length);
298            assert!(allocator.is_free(addr));
299        }
300
301        // Check that all prefix sets only have one range
302        for set in &allocator.v4 {
303            assert_eq!(1, set.free.ranges().len());
304        }
305        for set in &allocator.v6 {
306            assert_eq!(1, set.free.ranges().len());
307        }
308    }
309
310    #[test]
311    fn test_allocate_boundary() {
312        let testcases = vec![
313            // v4
314            (
315                // prefix
316                "192.168.0.0/24".parse().unwrap(),
317                // address outside lower boundary
318                IpAddr::from_str("192.167.255.255").unwrap(),
319                // address outside upper boundary
320                IpAddr::from_str("192.168.1.0").unwrap(),
321            ),
322            // v6
323            (
324                // prefix
325                "2001:db8::/64".parse().unwrap(),
326                // address outside lower boundary
327                IpAddr::from_str("2001:db7:ffff:ffff:ffff:ffff:ffff:ffff").unwrap(),
328                // address outside upper boundary
329                IpAddr::from_str("2001:db9::0").unwrap(),
330            ),
331        ];
332
333        for (prefix, lower, upper) in testcases {
334            let mut allocator =
335                AddressAllocator::new(vec![prefix], ChaCha8Rng::seed_from_u64(42)).unwrap();
336
337            // lower end of the range
338            let fail = allocator
339                .allocate(lower)
340                .expect_err("Should not be able to allocate");
341            assert!(matches!(fail, AddressAllocatorError::AddressNotInPrefix(_)),);
342
343            let addr = allocator
344                .allocate(prefix.network())
345                .expect("Failed to allocate");
346            assert!(!allocator.is_free(addr));
347            assert!(std::mem::discriminant(&addr) == std::mem::discriminant(&prefix.network()));
348
349            let fail = allocator
350                .allocate(prefix.network())
351                .expect_err("Should not be able to allocate");
352            assert!(matches!(
353                fail,
354                AddressAllocatorError::AddressAlreadyAllocated(_)
355            ));
356
357            // upper end of the range
358            let fail = allocator
359                .allocate(upper)
360                .expect_err("Should not be able to allocate");
361            assert!(matches!(fail, AddressAllocatorError::AddressNotInPrefix(_)));
362
363            let addr = allocator
364                .allocate(prefix.broadcast())
365                .expect("Failed to allocate");
366            assert!(!allocator.is_free(addr));
367            assert!(std::mem::discriminant(&addr) == std::mem::discriminant(&prefix.network()));
368
369            let fail = allocator
370                .allocate(prefix.broadcast())
371                .expect_err("Should not be able to allocate");
372            assert!(matches!(
373                fail,
374                AddressAllocatorError::AddressAlreadyAllocated(_)
375            ));
376        }
377    }
378
379    #[test]
380    fn test_allocate_full() {
381        let mut allocator = AddressAllocator::new(
382            vec![
383                "192.168.0.0/31".parse().unwrap(),
384                "192.168.0.2/31".parse().unwrap(),
385            ],
386            ChaCha8Rng::seed_from_u64(42),
387        )
388        .unwrap();
389        for _ in 0..4 {
390            let addr = allocator
391                .allocate(IpAddr::V4(Ipv4Addr::UNSPECIFIED))
392                .expect("Failed to allocate");
393            assert!(addr.is_ipv4());
394            check_allocator_invariants(&allocator);
395        }
396        assert_eq!(allocator.free_v4(), 0);
397        let fail = allocator
398            .allocate(IpAddr::V4(Ipv4Addr::UNSPECIFIED))
399            .expect_err("Should not be able to allocate");
400        assert!(matches!(fail, AddressAllocatorError::NoAddressesAvailable));
401    }
402}