use std::{
sync::RwLockWriteGuard,
time::{Duration, SystemTime},
};
use ipnet::IpNet;
use scion_proto::address::{EndhostAddr, IsdAsn};
use scion_sdk_address_manager::manager::AddressManager;
use scion_sdk_token_validator::validator::Token;
use snap_tokens::session_token::SessionTokenClaims;
use crate::state::{AllocationError, SharedPocketScionState, SnapDataPlaneId, SystemState};
pub(crate) struct StateSnapAddressAllocator {
state: SharedPocketScionState,
snap_data_plane_id: SnapDataPlaneId,
}
impl StateSnapAddressAllocator {
pub(crate) fn new(state: SharedPocketScionState, snap_data_plane_id: SnapDataPlaneId) -> Self {
Self {
state,
snap_data_plane_id,
}
}
fn mut_registry<'a>(
&self,
state_guard: &'a mut RwLockWriteGuard<'_, SystemState>,
isd_as: IsdAsn,
) -> Result<&'a mut AddressManager, AllocationError> {
let reg = state_guard
.snaps
.get_mut(&self.snap_data_plane_id.snap())
.expect("SNAP not found")
.data_planes
.get_mut(&self.snap_data_plane_id.data_plane())
.expect("SNAP data plane not found");
let reg = reg
.address_registries
.values_mut()
.find(|registry| registry.match_isd_as(isd_as))
.ok_or(AllocationError::NoAddressManagerForIsdAs(isd_as))?;
Ok(reg)
}
fn allocate_internal(
&self,
isd_as: IsdAsn,
prefix: IpNet,
id: String,
) -> Result<EndhostAddr, AllocationError> {
if prefix.prefix_len() != prefix.max_prefix_len() {
return Err(AllocationError::PrefixAllocationNotSupported(prefix));
}
let mut state_guard = self.state.system_state.write().unwrap();
let registry = self.mut_registry(&mut state_guard, isd_as)?;
let grant = registry.register(id, isd_as, prefix.addr())?;
Ok(EndhostAddr::new(grant.isd_as, grant.prefix.addr()))
}
fn hold_internal(&self, id: snap_tun::AddressAllocationId) -> bool {
let mut state_guard = self.state.system_state.write().unwrap();
let start_time: SystemTime = state_guard.start_time;
let Ok(registry) = self.mut_registry(&mut state_guard, id.isd_as) else {
return false;
};
registry.put_on_hold(
id.id,
SystemTime::now()
.duration_since(start_time)
.expect("system time went backwards"),
)
}
fn deallocate_internal(&self, id: snap_tun::AddressAllocationId) -> bool {
let mut state_guard = self.state.system_state.write().unwrap();
let Ok(registry) = self.mut_registry(&mut state_guard, id.isd_as) else {
return false;
};
registry.free(&id.id)
}
}
impl snap_tun::AddressAllocator<SessionTokenClaims> for StateSnapAddressAllocator {
fn allocate(
&self,
isd_as: IsdAsn,
prefix: IpNet,
claims: SessionTokenClaims,
) -> Result<snap_tun::AddressAllocation, snap_tun::AddressAllocationError> {
if claims.exp_time() < SystemTime::now() {
return Err(snap_tun::AddressAllocationError::AddressAllocationRejected);
}
let grant = self.allocate_internal(isd_as, prefix, claims.id())?;
Ok(snap_tun::AddressAllocation {
id: snap_tun::AddressAllocationId {
isd_as,
id: claims.id(),
},
address: grant,
})
}
fn put_on_hold(&self, id: snap_tun::AddressAllocationId) -> bool {
self.hold_internal(id)
}
fn deallocate(&self, id: snap_tun::AddressAllocationId) -> bool {
self.deallocate_internal(id)
}
}
impl SharedPocketScionState {
#[expect(unused)]
fn clean_all_address_registries(&self, time_since_start: Duration) {
let mut state_guard = self.system_state.write().unwrap();
state_guard
.snaps
.values_mut()
.flat_map(|s| s.data_planes.values_mut())
.flat_map(|d| d.address_registries.values_mut())
.for_each(|r| {
r.clean_expired(time_since_start);
});
}
}
#[cfg(test)]
mod tests {
use std::{
net::{IpAddr, Ipv4Addr, Ipv6Addr},
str::FromStr,
time::{Duration, UNIX_EPOCH},
};
use ipnet::{Ipv4Net, Ipv6Net};
use rand::SeedableRng;
use rand_chacha::ChaCha8Rng;
use snap_tokens::Pssid;
use snap_tun::{AddressAllocation, AddressAllocator};
use test_log::test;
use uuid::Uuid;
use super::*;
const IPV4_PREFIX: IpNet = IpNet::V4(Ipv4Net::new_assert(Ipv4Addr::new(10, 0, 0, 0), 8));
const IPV6_PREFIX: IpNet = IpNet::V6(Ipv6Net::new_assert(
Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0),
64,
));
fn check_wildcard_allocation(
res: Result<AddressAllocation, snap_tun::AddressAllocationError>,
isd_as: IsdAsn,
prefix: IpNet,
) {
let grant = res.expect("Address allocation failed");
assert_eq!(
grant.address.isd_asn(),
isd_as,
"Allocated address ISD-AS does not match"
);
assert!(
prefix.contains(&grant.address.local_address()),
"Allocated address is not within the expected prefix, prefix: {prefix}, address: {}",
grant.address.local_address()
);
}
fn check_explicit_allocation(
res: Result<AddressAllocation, snap_tun::AddressAllocationError>,
isd_as: IsdAsn,
expected: IpAddr,
) {
let grant = res.expect("Address allocation failed");
assert_eq!(
grant.address.isd_asn(),
isd_as,
"Allocated address ISD-AS does not match"
);
assert_eq!(
grant.address.local_address(),
expected,
"Allocated address is not the expected one"
);
}
#[test(tokio::test)]
async fn test_allocation() {
let now = SystemTime::now();
let mut state = SharedPocketScionState::new(now);
let isd_as = IsdAsn::from_str("1-ff00:0:110").unwrap();
let prefixes: Vec<IpNet> = vec![IPV4_PREFIX, IPV6_PREFIX];
let snap_id = state.add_snap();
let snap_dp_id =
state.add_snap_data_plane(snap_id, isd_as, prefixes, ChaCha8Rng::from_os_rng());
let allocator = StateSnapAddressAllocator::new(state, snap_dp_id);
let session_token = SessionTokenClaims {
pssid: Pssid(Uuid::new_v4()),
data_plane_id: 0,
exp: SystemTime::now()
.checked_add(Duration::from_secs(1000000000))
.unwrap()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs(),
};
let requested_ipv4 = Ipv4Addr::new(10, 0, 0, 10);
let result = allocator.allocate(
isd_as,
Ipv4Net::new_assert(requested_ipv4, 32).into(),
session_token.clone(),
);
check_explicit_allocation(result, isd_as, requested_ipv4.into());
let requested_ipv6 = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 10);
let result = allocator.allocate(
isd_as,
Ipv6Net::new_assert(requested_ipv6, 128).into(),
session_token.clone(),
);
check_explicit_allocation(result, isd_as, requested_ipv6.into());
let result = allocator.allocate(
isd_as,
IpAddr::V4(Ipv4Addr::UNSPECIFIED).into(),
session_token.clone(),
);
check_wildcard_allocation(result, isd_as, IPV4_PREFIX);
let result = allocator.allocate(
isd_as,
IpAddr::V6(Ipv6Addr::UNSPECIFIED).into(),
session_token.clone(),
);
check_wildcard_allocation(result, isd_as, IPV6_PREFIX);
let result = allocator.allocate(
isd_as,
Ipv4Net::new_assert(Ipv4Addr::new(10, 0, 0, 10), 30).into(),
session_token.clone(),
);
assert!(matches!(
result,
Err(snap_tun::AddressAllocationError::AddressAllocationRejected)
));
let result = allocator.allocate(
isd_as,
Ipv6Net::new_assert(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 10), 120).into(),
session_token.clone(),
);
assert!(matches!(
result,
Err(snap_tun::AddressAllocationError::AddressAllocationRejected)
));
}
}