1use std::{
17 collections::BTreeMap,
18 net::IpAddr,
19 time::{self, Duration},
20};
21
22use ipnet::IpNet;
23use rand_chacha::ChaCha8Rng;
24use scion_proto::address::{EndhostAddr, IsdAsn};
25use serde::{Deserialize, Serialize};
26use thiserror::Error;
27
28use crate::allocator::{AddressAllocator, AddressAllocatorError, AllocatorCreationError};
29
30pub mod dto;
31
32const DEFAULT_HOLD_DURATION: Duration = Duration::from_secs(600);
33const MAX_ATTEMPTS: usize = 10;
34
35#[derive(Debug, Error, PartialEq, Eq)]
37pub enum AddressRegistrationError {
38 #[error("requested address {0} already registered")]
40 AddressAlreadyRegistered(EndhostAddr),
41 #[error("requested ISD-AS {0} not in allocation ISD-AS {1}")]
43 IaNotInAllocationRange(IsdAsn, IsdAsn),
44 #[error("requested address before start time")]
46 AddressAllocatorError(#[from] AddressAllocatorError),
47}
48
49#[derive(PartialEq, Eq, Debug, Clone)]
51pub struct AddressGrant {
52 pub id: String,
54 pub isd_as: IsdAsn,
56 pub prefix: ipnet::IpNet,
58}
59
60#[derive(Debug, PartialEq, Serialize, Deserialize, Eq, Clone)]
62struct AddressGrantEntry {
63 id: String,
65 endhost_address: EndhostAddr,
67 on_hold_expiry: Option<Duration>,
69}
70
71#[derive(Debug, PartialEq, Eq, Clone)]
73pub struct AddressManager {
74 isd_as: IsdAsn,
75 hold_duration: time::Duration,
76 address_grants: BTreeMap<String, AddressGrantEntry>,
78 free_ips: AddressAllocator,
79 max_attempts: usize,
80 prefixes: Vec<IpNet>,
81}
82
83impl AddressManager {
84 pub fn new(
86 isd_as: IsdAsn,
87 prefixes: Vec<IpNet>,
88 rng: ChaCha8Rng,
89 ) -> Result<Self, AllocatorCreationError> {
90 let free_ips = AddressAllocator::new(prefixes.clone(), rng)?;
91 Ok(Self {
92 isd_as,
93 hold_duration: DEFAULT_HOLD_DURATION,
94 address_grants: Default::default(),
95 max_attempts: MAX_ATTEMPTS,
96 free_ips,
97 prefixes,
98 })
99 }
100
101 pub fn with_hold_duration(mut self, hold_duration: time::Duration) -> Self {
103 self.hold_duration = hold_duration;
104 self
105 }
106
107 pub fn with_max_attempts(mut self, max_attempts: usize) -> Self {
109 self.max_attempts = max_attempts;
110 self
111 }
112
113 pub fn match_isd_as(&self, isd_as: IsdAsn) -> bool {
115 if isd_as.isd().is_wildcard() && isd_as.asn().is_wildcard() {
116 true
117 } else if isd_as.isd().is_wildcard() {
118 isd_as.asn() == self.isd_as.asn()
119 } else if isd_as.asn().is_wildcard() {
120 isd_as.isd() == self.isd_as.isd()
121 } else {
122 isd_as == self.isd_as
123 }
124 }
125
126 pub fn register(
134 &mut self,
135 id: String,
136 mut isd_asn: IsdAsn,
137 addr: IpAddr,
138 ) -> Result<AddressGrant, AddressRegistrationError> {
139 if isd_asn.asn().is_wildcard() {
140 isd_asn.set_asn(self.isd_as.asn());
141 }
142 if isd_asn.isd().is_wildcard() {
143 isd_asn.set_isd(self.isd_as.isd());
144 }
145
146 if isd_asn != self.isd_as {
147 return Err(AddressRegistrationError::IaNotInAllocationRange(
148 isd_asn,
149 self.isd_as,
150 ));
151 }
152
153 let endhost_address = match (addr.is_unspecified(), self.address_grants.get(&id)) {
154 (true, Some(existing))
156 if existing.endhost_address.local_address().is_ipv4() == addr.is_ipv4() =>
158 {
159 existing.endhost_address
160 }
161 (false, Some(existing)) if existing.endhost_address.local_address() == addr => {
162 existing.endhost_address
163 }
164 (..) => {
167 let ip = self.free_ips.allocate(addr)?;
168 EndhostAddr::new(isd_asn, ip)
169 }
170 };
171
172 let grant = AddressGrantEntry {
173 id: id.clone(),
174 on_hold_expiry: None,
175 endhost_address,
176 };
177
178 if let Some(removed) = self.address_grants.insert(id, grant.clone()) {
180 if removed.endhost_address != endhost_address {
181 if let Err(e) = self.free_ips.free(removed.endhost_address.local_address()) {
182 tracing::error!(
183 "Allocator did not contain ip from removed entry - this should not happen: {e}"
184 )
185 };
186 };
187 }
188
189 Ok(AddressGrant {
190 id: grant.id,
191 isd_as: grant.endhost_address.isd_asn(),
192 prefix: grant.endhost_address.local_address().into(),
193 })
194 }
195
196 pub fn put_on_hold(&mut self, id: String, duration_since_start: Duration) -> bool {
200 match self.address_grants.get_mut(&id) {
201 Some(grant) => {
202 grant.on_hold_expiry = Some(duration_since_start + self.hold_duration);
203
204 true
205 }
206 None => false,
207 }
208 }
209
210 pub fn free(&mut self, id: &String) -> bool {
214 match self.address_grants.remove(id) {
215 Some(removed) => {
216 if let Err(e) = self.free_ips.free(removed.endhost_address.local_address()) {
217 tracing::warn!(
218 "Address allocator did not contain an existing address grant - this should never happen: {e}"
219 );
220 };
221
222 true
223 }
224 None => false,
225 }
226 }
227
228 pub fn isd_asn(&self) -> IsdAsn {
230 self.isd_as
231 }
232
233 pub fn prefixes(&self) -> &[IpNet] {
235 &self.prefixes
236 }
237
238 pub fn min_free_addresses(&self) -> u128 {
242 self.free_ips.free_v4() + self.free_ips.free_v6()
243 }
244
245 pub fn clean_expired(&mut self, duration_since_start: Duration) -> (usize, usize) {
251 let start_grant_count = self.address_grants.len();
252
253 self.address_grants.retain(|_, grant| {
254 let Some(on_hold_until) = grant.on_hold_expiry else {
255 return true; };
257
258 if duration_since_start > on_hold_until {
260 self.free_ips
262 .free(grant.endhost_address.local_address())
263 .unwrap();
264
265 return false;
267 }
268
269 true
270 });
271
272 let end_grant_count = self.address_grants.len();
273 (start_grant_count, end_grant_count)
274 }
275}
276
277#[cfg(test)]
278mod tests {
279 use std::{
280 net::{Ipv4Addr, Ipv6Addr},
281 str::FromStr,
282 };
283
284 use rand::SeedableRng;
285 use rand_chacha::ChaCha8Rng;
286
287 use super::*;
288
289 fn duration(offset: u64) -> Duration {
290 Duration::from_secs(offset)
291 }
292
293 fn get_registry() -> AddressManager {
294 AddressManager::new(
295 IsdAsn::from_str("1-ff00:0:110").unwrap(),
296 vec![
297 IpNet::from_str("192.168.0.0/24").unwrap(),
298 IpNet::from_str("2001:db8::/64").unwrap(),
299 ],
300 ChaCha8Rng::seed_from_u64(42),
301 )
302 .unwrap()
303 }
304
305 fn id() -> String {
306 "00000000-0000-0000-0000-000000000001".to_string()
307 }
308
309 fn get_request(ip: &str) -> (IsdAsn, IpAddr) {
310 (
311 IsdAsn::from_str("1-ff00:0:110").unwrap(),
312 IpAddr::from_str(ip).unwrap(),
313 )
314 }
315
316 fn register(
317 id: String,
318 request: (IsdAsn, IpAddr),
319 ) -> Result<AddressGrant, AddressRegistrationError> {
320 get_registry().register(id, request.0, request.1)
321 }
322
323 #[test]
324 fn should_fail_on_isd_as_mismatch() {
325 let result = register(
326 id(),
327 (
328 IsdAsn::from_str("1-ff00:0:111").unwrap(),
329 "192.168.0.0".parse().unwrap(),
330 ),
331 );
332 assert_eq!(
333 result,
334 Err(AddressRegistrationError::IaNotInAllocationRange(
335 IsdAsn::from_str("1-ff00:0:111").unwrap(),
336 IsdAsn::from_str("1-ff00:0:110").unwrap()
337 ))
338 );
339 }
340
341 #[test]
342 fn should_fail_if_ipv4_is_outside_range() {
343 let result = register(id(), get_request("192.168.1.0"));
344 assert_eq!(
345 result,
346 Err(AddressRegistrationError::AddressAllocatorError(
347 AddressAllocatorError::AddressNotInPrefix("192.168.1.0".parse().unwrap())
348 ))
349 );
350 }
351
352 #[test]
353 fn should_fail_if_ipv6_is_outside_range() {
354 let result = register(id(), get_request("2001:db9::"));
355 assert_eq!(
356 result,
357 Err(AddressRegistrationError::AddressAllocatorError(
358 AddressAllocatorError::AddressNotInPrefix("2001:db9::".parse().unwrap())
359 ))
360 );
361 }
362
363 #[test]
364 fn should_succeed_to_re_register_same_ip_and_fail_register_same_ip_with_different_id() {
365 let isd_as = IsdAsn::from_str("1-ff00:0:110").unwrap();
366 let mut registry = get_registry();
367 {
369 let v4 = "192.168.0.0".parse().unwrap();
370 registry.register(id(), isd_as, v4).expect("Should Succeed");
371
372 registry.register(id(), isd_as, v4).expect("Should Succeed");
374
375 let other_id = "Other Id".to_string();
377 let result = registry.register(other_id, isd_as, v4);
378 assert_eq!(
379 result,
380 Err(AddressRegistrationError::AddressAllocatorError(
381 AddressAllocatorError::AddressAlreadyAllocated(v4)
382 )),
383 "got {result:?}"
384 );
385 }
386
387 {
389 let mut registry = get_registry();
390 let v6 = "2001:db8::".parse().unwrap();
391 registry.register(id(), isd_as, v6).expect("Should Succeed");
392
393 registry.register(id(), isd_as, v6).expect("Should Succeed");
395
396 let other_id = "Other Id".to_string();
398 let result = registry.register(other_id, isd_as, v6);
399 assert_eq!(
400 result,
401 Err(AddressRegistrationError::AddressAllocatorError(
402 AddressAllocatorError::AddressAlreadyAllocated(v6)
403 ))
404 );
405 }
406 }
407
408 #[test]
409 fn should_fail_if_no_address_is_available() {
410 let isd_as = IsdAsn::from_str("1-ff00:0:110").unwrap();
411 let mut registry = AddressManager::new(
412 IsdAsn::from_str("1-ff00:0:110").unwrap(),
413 vec![],
414 ChaCha8Rng::seed_from_u64(42),
415 )
416 .unwrap();
417 let v4 = Ipv4Addr::UNSPECIFIED.into();
419 let result = registry.register(id(), isd_as, v4);
420 assert_eq!(
421 result,
422 Err(AddressRegistrationError::AddressAllocatorError(
423 AddressAllocatorError::NoAddressesAvailable
424 ))
425 );
426
427 let v6 = Ipv6Addr::UNSPECIFIED.into();
429 let result = registry.register(id(), isd_as, v6);
430 assert_eq!(
431 result,
432 Err(AddressRegistrationError::AddressAllocatorError(
433 AddressAllocatorError::NoAddressesAvailable
434 ))
435 );
436 }
437
438 #[test]
439 fn should_succeed_allocation_with_specific_ip() {
440 let isd_as = IsdAsn::from_str("1-ff00:0:110").unwrap();
441 let mut registry = get_registry();
442 let v4 = "192.168.0.0".parse().unwrap();
444 let result = registry.register(id(), isd_as, v4).expect("Should succeed");
445 assert_eq!(result.prefix.addr(), v4, "Expected specific assignment");
446
447 let v6 = "2001:db8::".parse().unwrap();
449 let result = registry.register(id(), isd_as, v6).expect("Should succeed");
450 assert_eq!(result.prefix.addr(), v6, "Expected specific assignment");
451 }
452
453 #[test]
454 fn should_succeed_allocation_with_wildcard() {
455 let mut registry = get_registry();
456 let result = registry.register(id(), IsdAsn::WILDCARD, Ipv4Addr::UNSPECIFIED.into());
458 assert!(result.is_ok());
459
460 let result = registry.register(id(), IsdAsn::WILDCARD, Ipv6Addr::UNSPECIFIED.into());
462 assert!(result.is_ok());
463 }
464
465 #[test]
466 fn should_clean_existing_grant_on_reallocation() {
467 let mut registry = get_registry();
468 let initial = "192.168.0.0".parse().unwrap();
469 let other = "2001:db8::".parse().unwrap();
470
471 assert!(
472 registry.free_ips.is_free(initial),
473 "Expected initial address to be free"
474 );
475
476 for _ in 0..10 {
478 let grant = registry
479 .register(id(), IsdAsn::WILDCARD, initial)
480 .expect("Should succeed");
481
482 assert_eq!(
483 grant.prefix.addr(),
484 initial,
485 "Expected assignment of given address"
486 );
487
488 assert!(
489 !registry.free_ips.is_free(initial),
490 "Expected initial address to not be free"
491 );
492
493 assert!(
494 registry.free_ips.is_free(other),
495 "Expected other address to be free"
496 );
497 registry
499 .register(id(), IsdAsn::WILDCARD, other)
500 .expect("Should succeed");
501
502 assert!(
503 registry.free_ips.is_free(initial),
504 "Expected initial address to have been freed"
505 );
506 }
507 }
508
509 #[test]
510 fn should_not_assign_on_hold_address() {
511 let mut registry = get_registry();
512 let initial = "192.168.0.0".parse().unwrap();
513
514 let _grant = registry
515 .register(id(), IsdAsn::WILDCARD, initial)
516 .expect("Should succeed");
517
518 registry.put_on_hold(id(), duration(0));
519
520 let other_id = "Other Id".to_string();
521 let result = registry.register(other_id.clone(), IsdAsn::WILDCARD, initial);
522 assert_eq!(
523 result,
524 Err(AddressRegistrationError::AddressAllocatorError(
525 AddressAllocatorError::AddressAlreadyAllocated(initial)
526 )),
527 "Should have given AddressAlreadyAllocated got {result:?}"
528 );
529 }
530
531 #[test]
532 fn should_clean_expired_grants() {
533 let mut registry = get_registry();
534 let initial = "192.168.0.0".parse().unwrap();
535 let grant = registry
536 .register(id(), IsdAsn::WILDCARD, initial)
537 .expect("Should succeed");
538
539 assert!(registry.put_on_hold(grant.id, duration(0)));
540
541 let (before, after) =
542 registry.clean_expired(registry.hold_duration + Duration::from_nanos(1));
543 assert_eq!(before, 1);
544 assert_eq!(after, 0, "Expected grant to have been cleaned");
545 assert!(
546 registry.free_ips.is_free(initial),
547 "Expected initial address to have been freed"
548 );
549 }
550
551 #[test]
552 fn should_keep_not_expired_grants() {
553 let mut registry = get_registry();
554
555 let initial = "192.168.0.0".parse().unwrap();
556 let grant = registry
557 .register(id(), IsdAsn::WILDCARD, initial)
558 .expect("Should succeed");
559
560 assert!(registry.put_on_hold(grant.id, duration(0)));
561
562 let (before, after) = registry.clean_expired(registry.hold_duration);
563 assert_eq!(before, 1);
564 assert_eq!(after, 1);
565 }
566}