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 error = %e,
184 "Allocator did not contain IP from removed entry - this should not happen"
185 )
186 };
187 };
188 }
189
190 Ok(AddressGrant {
191 id: grant.id,
192 isd_as: grant.endhost_address.isd_asn(),
193 prefix: grant.endhost_address.local_address().into(),
194 })
195 }
196
197 pub fn put_on_hold(&mut self, id: String, duration_since_start: Duration) -> bool {
201 match self.address_grants.get_mut(&id) {
202 Some(grant) => {
203 grant.on_hold_expiry = Some(duration_since_start + self.hold_duration);
204
205 true
206 }
207 None => false,
208 }
209 }
210
211 pub fn free(&mut self, id: &String) -> bool {
215 match self.address_grants.remove(id) {
216 Some(removed) => {
217 if let Err(e) = self.free_ips.free(removed.endhost_address.local_address()) {
218 tracing::warn!(
219 error = %e,
220 "Address allocator did not contain an existing address grant - this should never happen"
221 );
222 };
223
224 true
225 }
226 None => false,
227 }
228 }
229
230 pub fn isd_asn(&self) -> IsdAsn {
232 self.isd_as
233 }
234
235 pub fn prefixes(&self) -> &[IpNet] {
237 &self.prefixes
238 }
239
240 pub fn min_free_addresses(&self) -> u128 {
244 self.free_ips.free_v4() + self.free_ips.free_v6()
245 }
246
247 pub fn clean_expired(&mut self, duration_since_start: Duration) -> (usize, usize) {
253 let start_grant_count = self.address_grants.len();
254
255 self.address_grants.retain(|_, grant| {
256 let Some(on_hold_until) = grant.on_hold_expiry else {
257 return true; };
259
260 if duration_since_start > on_hold_until {
262 self.free_ips
264 .free(grant.endhost_address.local_address())
265 .unwrap();
266
267 return false;
269 }
270
271 true
272 });
273
274 let end_grant_count = self.address_grants.len();
275 (start_grant_count, end_grant_count)
276 }
277}
278
279#[cfg(test)]
280mod tests {
281 use std::{
282 net::{Ipv4Addr, Ipv6Addr},
283 str::FromStr,
284 };
285
286 use rand::SeedableRng;
287 use rand_chacha::ChaCha8Rng;
288
289 use super::*;
290
291 fn duration(offset: u64) -> Duration {
292 Duration::from_secs(offset)
293 }
294
295 fn get_registry() -> AddressManager {
296 AddressManager::new(
297 IsdAsn::from_str("1-ff00:0:110").unwrap(),
298 vec![
299 IpNet::from_str("192.168.0.0/24").unwrap(),
300 IpNet::from_str("2001:db8::/64").unwrap(),
301 ],
302 ChaCha8Rng::seed_from_u64(42),
303 )
304 .unwrap()
305 }
306
307 fn id() -> String {
308 "00000000-0000-0000-0000-000000000001".to_string()
309 }
310
311 fn get_request(ip: &str) -> (IsdAsn, IpAddr) {
312 (
313 IsdAsn::from_str("1-ff00:0:110").unwrap(),
314 IpAddr::from_str(ip).unwrap(),
315 )
316 }
317
318 fn register(
319 id: String,
320 request: (IsdAsn, IpAddr),
321 ) -> Result<AddressGrant, AddressRegistrationError> {
322 get_registry().register(id, request.0, request.1)
323 }
324
325 #[test]
326 fn should_fail_on_isd_as_mismatch() {
327 let result = register(
328 id(),
329 (
330 IsdAsn::from_str("1-ff00:0:111").unwrap(),
331 "192.168.0.0".parse().unwrap(),
332 ),
333 );
334 assert_eq!(
335 result,
336 Err(AddressRegistrationError::IaNotInAllocationRange(
337 IsdAsn::from_str("1-ff00:0:111").unwrap(),
338 IsdAsn::from_str("1-ff00:0:110").unwrap()
339 ))
340 );
341 }
342
343 #[test]
344 fn should_fail_if_ipv4_is_outside_range() {
345 let result = register(id(), get_request("192.168.1.0"));
346 assert_eq!(
347 result,
348 Err(AddressRegistrationError::AddressAllocatorError(
349 AddressAllocatorError::AddressNotInPrefix("192.168.1.0".parse().unwrap())
350 ))
351 );
352 }
353
354 #[test]
355 fn should_fail_if_ipv6_is_outside_range() {
356 let result = register(id(), get_request("2001:db9::"));
357 assert_eq!(
358 result,
359 Err(AddressRegistrationError::AddressAllocatorError(
360 AddressAllocatorError::AddressNotInPrefix("2001:db9::".parse().unwrap())
361 ))
362 );
363 }
364
365 #[test]
366 fn should_succeed_to_re_register_same_ip_and_fail_register_same_ip_with_different_id() {
367 let isd_as = IsdAsn::from_str("1-ff00:0:110").unwrap();
368 let mut registry = get_registry();
369 {
371 let v4 = "192.168.0.0".parse().unwrap();
372 registry.register(id(), isd_as, v4).expect("Should Succeed");
373
374 registry.register(id(), isd_as, v4).expect("Should Succeed");
376
377 let other_id = "Other Id".to_string();
379 let result = registry.register(other_id, isd_as, v4);
380 assert_eq!(
381 result,
382 Err(AddressRegistrationError::AddressAllocatorError(
383 AddressAllocatorError::AddressAlreadyAllocated(v4)
384 )),
385 "got {result:?}"
386 );
387 }
388
389 {
391 let mut registry = get_registry();
392 let v6 = "2001:db8::".parse().unwrap();
393 registry.register(id(), isd_as, v6).expect("Should Succeed");
394
395 registry.register(id(), isd_as, v6).expect("Should Succeed");
397
398 let other_id = "Other Id".to_string();
400 let result = registry.register(other_id, isd_as, v6);
401 assert_eq!(
402 result,
403 Err(AddressRegistrationError::AddressAllocatorError(
404 AddressAllocatorError::AddressAlreadyAllocated(v6)
405 ))
406 );
407 }
408 }
409
410 #[test]
411 fn should_fail_if_no_address_is_available() {
412 let isd_as = IsdAsn::from_str("1-ff00:0:110").unwrap();
413 let mut registry = AddressManager::new(
414 IsdAsn::from_str("1-ff00:0:110").unwrap(),
415 vec![],
416 ChaCha8Rng::seed_from_u64(42),
417 )
418 .unwrap();
419 let v4 = Ipv4Addr::UNSPECIFIED.into();
421 let result = registry.register(id(), isd_as, v4);
422 assert_eq!(
423 result,
424 Err(AddressRegistrationError::AddressAllocatorError(
425 AddressAllocatorError::NoAddressesAvailable
426 ))
427 );
428
429 let v6 = Ipv6Addr::UNSPECIFIED.into();
431 let result = registry.register(id(), isd_as, v6);
432 assert_eq!(
433 result,
434 Err(AddressRegistrationError::AddressAllocatorError(
435 AddressAllocatorError::NoAddressesAvailable
436 ))
437 );
438 }
439
440 #[test]
441 fn should_succeed_allocation_with_specific_ip() {
442 let isd_as = IsdAsn::from_str("1-ff00:0:110").unwrap();
443 let mut registry = get_registry();
444 let v4 = "192.168.0.0".parse().unwrap();
446 let result = registry.register(id(), isd_as, v4).expect("Should succeed");
447 assert_eq!(result.prefix.addr(), v4, "Expected specific assignment");
448
449 let v6 = "2001:db8::".parse().unwrap();
451 let result = registry.register(id(), isd_as, v6).expect("Should succeed");
452 assert_eq!(result.prefix.addr(), v6, "Expected specific assignment");
453 }
454
455 #[test]
456 fn should_succeed_allocation_with_wildcard() {
457 let mut registry = get_registry();
458 let result = registry.register(id(), IsdAsn::WILDCARD, Ipv4Addr::UNSPECIFIED.into());
460 assert!(result.is_ok());
461
462 let result = registry.register(id(), IsdAsn::WILDCARD, Ipv6Addr::UNSPECIFIED.into());
464 assert!(result.is_ok());
465 }
466
467 #[test]
468 fn should_clean_existing_grant_on_reallocation() {
469 let mut registry = get_registry();
470 let initial = "192.168.0.0".parse().unwrap();
471 let other = "2001:db8::".parse().unwrap();
472
473 assert!(
474 registry.free_ips.is_free(initial),
475 "Expected initial address to be free"
476 );
477
478 for _ in 0..10 {
480 let grant = registry
481 .register(id(), IsdAsn::WILDCARD, initial)
482 .expect("Should succeed");
483
484 assert_eq!(
485 grant.prefix.addr(),
486 initial,
487 "Expected assignment of given address"
488 );
489
490 assert!(
491 !registry.free_ips.is_free(initial),
492 "Expected initial address to not be free"
493 );
494
495 assert!(
496 registry.free_ips.is_free(other),
497 "Expected other address to be free"
498 );
499 registry
501 .register(id(), IsdAsn::WILDCARD, other)
502 .expect("Should succeed");
503
504 assert!(
505 registry.free_ips.is_free(initial),
506 "Expected initial address to have been freed"
507 );
508 }
509 }
510
511 #[test]
512 fn should_not_assign_on_hold_address() {
513 let mut registry = get_registry();
514 let initial = "192.168.0.0".parse().unwrap();
515
516 let _grant = registry
517 .register(id(), IsdAsn::WILDCARD, initial)
518 .expect("Should succeed");
519
520 registry.put_on_hold(id(), duration(0));
521
522 let other_id = "Other Id".to_string();
523 let result = registry.register(other_id.clone(), IsdAsn::WILDCARD, initial);
524 assert_eq!(
525 result,
526 Err(AddressRegistrationError::AddressAllocatorError(
527 AddressAllocatorError::AddressAlreadyAllocated(initial)
528 )),
529 "Should have given AddressAlreadyAllocated got {result:?}"
530 );
531 }
532
533 #[test]
534 fn should_clean_expired_grants() {
535 let mut registry = get_registry();
536 let initial = "192.168.0.0".parse().unwrap();
537 let grant = registry
538 .register(id(), IsdAsn::WILDCARD, initial)
539 .expect("Should succeed");
540
541 assert!(registry.put_on_hold(grant.id, duration(0)));
542
543 let (before, after) =
544 registry.clean_expired(registry.hold_duration + Duration::from_nanos(1));
545 assert_eq!(before, 1);
546 assert_eq!(after, 0, "Expected grant to have been cleaned");
547 assert!(
548 registry.free_ips.is_free(initial),
549 "Expected initial address to have been freed"
550 );
551 }
552
553 #[test]
554 fn should_keep_not_expired_grants() {
555 let mut registry = get_registry();
556
557 let initial = "192.168.0.0".parse().unwrap();
558 let grant = registry
559 .register(id(), IsdAsn::WILDCARD, initial)
560 .expect("Should succeed");
561
562 assert!(registry.put_on_hold(grant.id, duration(0)));
563
564 let (before, after) = registry.clean_expired(registry.hold_duration);
565 assert_eq!(before, 1);
566 assert_eq!(after, 1);
567 }
568}