use std::{
collections::{HashMap, HashSet},
net::{self},
sync::Arc,
time::Duration,
};
use arc_swap::ArcSwap;
use endhost_api_client::client::EndhostApiClient;
use scion_proto::{address::IsdAsn, path::PathInterface};
use scion_sdk_reqwest_connect_rpc::client::CrpcClientError;
use scion_sdk_utils::backoff::ExponentialBackoff;
use tokio::task::JoinHandle;
use url::Url;
pub trait UnderlayDiscovery: Send + Sync {
fn underlays(&self, isd_as: IsdAsn) -> Vec<(IsdAsn, UnderlayInfo)>;
fn isd_ases(&self) -> HashSet<IsdAsn>;
fn resolve_udp_underlay_next_hop(&self, interface: PathInterface) -> Option<net::SocketAddr>;
}
#[derive(Clone, Debug)]
pub struct ScionRouter {
internal_interface: net::SocketAddr,
interfaces: Vec<u16>,
}
#[derive(Clone, Debug)]
pub enum UnderlayInfo {
Snap(Url),
Udp(Vec<ScionRouter>),
}
struct PeriodicUnderlayDiscoveryInner {
pub underlays: ArcSwap<Vec<(IsdAsn, UnderlayInfo)>>,
pub udp_underlay_next_hops: ArcSwap<HashMap<PathInterface, net::SocketAddr>>,
}
pub struct PeriodicUnderlayDiscovery {
inner: Arc<PeriodicUnderlayDiscoveryInner>,
task: JoinHandle<()>,
}
impl Drop for PeriodicUnderlayDiscovery {
fn drop(&mut self) {
self.task.abort();
}
}
impl UnderlayDiscovery for PeriodicUnderlayDiscovery {
fn underlays(&self, isd_as: IsdAsn) -> Vec<(IsdAsn, UnderlayInfo)> {
self.inner
.underlays
.load()
.iter()
.filter(|(ia, _)| isd_as.matches(*ia))
.map(|(ia, info)| (*ia, info.clone()))
.collect()
}
fn isd_ases(&self) -> HashSet<IsdAsn> {
HashSet::from_iter(self.inner.underlays.load().iter().map(|(ia, _)| *ia))
}
fn resolve_udp_underlay_next_hop(&self, interface: PathInterface) -> Option<net::SocketAddr> {
self.inner
.udp_underlay_next_hops
.load()
.get(&interface)
.cloned()
}
}
impl PeriodicUnderlayDiscovery {
pub async fn new(
api_client: Arc<dyn EndhostApiClient>,
fetch_interval: Duration,
backoff: ExponentialBackoff,
) -> Result<Self, CrpcClientError> {
let (initial_underlays, initial_udp_underlay_next_hops) =
discover_underlays(&api_client).await?;
tracing::debug!(
underlays=?initial_underlays,
"Successfully discovered initial underlays"
);
let inner = Arc::new(PeriodicUnderlayDiscoveryInner {
underlays: ArcSwap::new(Arc::new(initial_underlays)),
udp_underlay_next_hops: ArcSwap::new(Arc::new(initial_udp_underlay_next_hops)),
});
let inner_clone = inner.clone();
let task = tokio::spawn(async move {
loop {
tokio::time::sleep(fetch_interval).await;
let mut failed_attempts = 0;
loop {
match discover_underlays(&api_client).await {
Ok((underlays, udp_underlay_next_hops)) => {
tracing::debug!(
underlays=?underlays,
"Successfully discovered underlays"
);
inner_clone.underlays.store(Arc::new(underlays));
inner_clone
.udp_underlay_next_hops
.store(Arc::new(udp_underlay_next_hops));
break;
}
Err(e) => {
failed_attempts += 1;
tracing::warn!(err = ?e, attempt = failed_attempts, "Failed to discover underlays");
tokio::time::sleep(backoff.duration(failed_attempts)).await;
}
}
}
}
});
Ok(Self { inner, task })
}
}
async fn discover_underlays(
api_client: &Arc<dyn EndhostApiClient>,
) -> Result<
(
Vec<(IsdAsn, UnderlayInfo)>,
HashMap<PathInterface, net::SocketAddr>,
),
CrpcClientError,
> {
let res = api_client.list_underlays(IsdAsn::WILDCARD.into()).await?;
let mut udp_underlays = HashMap::new();
for underlay in res.udp_underlay.into_iter() {
let entry = udp_underlays.entry(underlay.isd_as).or_insert(vec![]);
entry.push(ScionRouter {
internal_interface: underlay.internal_interface,
interfaces: underlay.interfaces,
});
}
let mut udp_underlay_next_hops = HashMap::new();
for (isd_as, routers) in udp_underlays.iter() {
for router in routers.iter() {
for interface_id in router.interfaces.iter() {
udp_underlay_next_hops.insert(
PathInterface {
isd_asn: (*isd_as).into(),
id: *interface_id,
},
router.internal_interface,
);
}
}
}
let mut underlays: Vec<(IsdAsn, UnderlayInfo)> = udp_underlays
.into_iter()
.map(|(isd_as, routers)| (isd_as.into(), UnderlayInfo::Udp(routers)))
.collect();
for underlay in res.snap_underlay.iter() {
for isd_as in underlay.isd_ases.iter() {
underlays.push((
(*isd_as).into(),
UnderlayInfo::Snap(underlay.address.clone()),
));
}
}
Ok((underlays, udp_underlay_next_hops))
}