scion_stack/underlays/
discovery.rs1use std::{
16 collections::{HashMap, HashSet},
17 net::{self},
18 sync::Arc,
19 time::Duration,
20};
21
22use arc_swap::ArcSwap;
23use endhost_api_client::client::EndhostApiClient;
24use scion_proto::{address::IsdAsn, path::PathInterface};
25use scion_sdk_reqwest_connect_rpc::client::CrpcClientError;
26use scion_sdk_utils::backoff::ExponentialBackoff;
27use tokio::task::JoinHandle;
28use url::Url;
29
30pub trait UnderlayDiscovery: Send + Sync {
34 fn underlays(&self, isd_as: IsdAsn) -> Vec<(IsdAsn, UnderlayInfo)>;
38 fn isd_ases(&self) -> HashSet<IsdAsn>;
40 fn resolve_udp_underlay_next_hop(&self, interface: PathInterface) -> Option<net::SocketAddr>;
42}
43
44#[derive(Clone, Debug)]
46pub struct ScionRouter {
47 internal_interface: net::SocketAddr,
49 interfaces: Vec<u16>,
51}
52
53#[derive(Clone, Debug)]
55pub enum UnderlayInfo {
56 Snap(Url),
58 Udp(Vec<ScionRouter>),
60}
61
62struct PeriodicUnderlayDiscoveryInner {
63 pub underlays: ArcSwap<Vec<(IsdAsn, UnderlayInfo)>>,
64 pub udp_underlay_next_hops: ArcSwap<HashMap<PathInterface, net::SocketAddr>>,
66}
67
68pub struct PeriodicUnderlayDiscovery {
72 inner: Arc<PeriodicUnderlayDiscoveryInner>,
73 task: JoinHandle<()>,
74}
75
76impl Drop for PeriodicUnderlayDiscovery {
77 fn drop(&mut self) {
78 self.task.abort();
79 }
80}
81
82impl UnderlayDiscovery for PeriodicUnderlayDiscovery {
83 fn underlays(&self, isd_as: IsdAsn) -> Vec<(IsdAsn, UnderlayInfo)> {
84 self.inner
85 .underlays
86 .load()
87 .iter()
88 .filter(|(ia, _)| isd_as.matches(*ia))
89 .map(|(ia, info)| (*ia, info.clone()))
90 .collect()
91 }
92
93 fn isd_ases(&self) -> HashSet<IsdAsn> {
94 HashSet::from_iter(self.inner.underlays.load().iter().map(|(ia, _)| *ia))
95 }
96
97 fn resolve_udp_underlay_next_hop(&self, interface: PathInterface) -> Option<net::SocketAddr> {
98 self.inner
99 .udp_underlay_next_hops
100 .load()
101 .get(&interface)
102 .cloned()
103 }
104}
105
106impl PeriodicUnderlayDiscovery {
107 pub async fn new(
110 api_client: Arc<dyn EndhostApiClient>,
111 fetch_interval: Duration,
112 backoff: ExponentialBackoff,
113 ) -> Result<Self, CrpcClientError> {
114 let (initial_underlays, initial_udp_underlay_next_hops) =
115 discover_underlays(&api_client).await?;
116 tracing::debug!(
117 underlays=?initial_underlays,
118 "Successfully discovered initial underlays"
119 );
120 let inner = Arc::new(PeriodicUnderlayDiscoveryInner {
121 underlays: ArcSwap::new(Arc::new(initial_underlays)),
122 udp_underlay_next_hops: ArcSwap::new(Arc::new(initial_udp_underlay_next_hops)),
123 });
124
125 let inner_clone = inner.clone();
126 let task = tokio::spawn(async move {
127 loop {
128 tokio::time::sleep(fetch_interval).await;
129 let mut failed_attempts = 0;
130 loop {
131 match discover_underlays(&api_client).await {
132 Ok((underlays, udp_underlay_next_hops)) => {
133 tracing::debug!(
134 underlays=?underlays,
135 "Successfully discovered underlays"
136 );
137 inner_clone.underlays.store(Arc::new(underlays));
138 inner_clone
139 .udp_underlay_next_hops
140 .store(Arc::new(udp_underlay_next_hops));
141 break;
142 }
143 Err(e) => {
144 failed_attempts += 1;
145 tracing::warn!(err = ?e, attempt = failed_attempts, "Failed to discover underlays");
146 tokio::time::sleep(backoff.duration(failed_attempts)).await;
147 }
148 }
149 }
150 }
151 });
152
153 Ok(Self { inner, task })
154 }
155}
156
157async fn discover_underlays(
158 api_client: &Arc<dyn EndhostApiClient>,
159) -> Result<
160 (
161 Vec<(IsdAsn, UnderlayInfo)>,
162 HashMap<PathInterface, net::SocketAddr>,
163 ),
164 CrpcClientError,
165> {
166 let res = api_client.list_underlays(IsdAsn::WILDCARD.into()).await?;
167 let mut udp_underlays = HashMap::new();
168 for underlay in res.udp_underlay.into_iter() {
169 let entry = udp_underlays.entry(underlay.isd_as).or_insert(vec![]);
170 entry.push(ScionRouter {
171 internal_interface: underlay.internal_interface,
172 interfaces: underlay.interfaces,
173 });
174 }
175
176 let mut udp_underlay_next_hops = HashMap::new();
178 for (isd_as, routers) in udp_underlays.iter() {
179 for router in routers.iter() {
180 for interface_id in router.interfaces.iter() {
181 udp_underlay_next_hops.insert(
182 PathInterface {
183 isd_asn: (*isd_as).into(),
184 id: *interface_id,
185 },
186 router.internal_interface,
187 );
188 }
189 }
190 }
191
192 let mut underlays: Vec<(IsdAsn, UnderlayInfo)> = udp_underlays
194 .into_iter()
195 .map(|(isd_as, routers)| (isd_as.into(), UnderlayInfo::Udp(routers)))
196 .collect();
197 for underlay in res.snap_underlay.iter() {
198 for isd_as in underlay.isd_ases.iter() {
199 underlays.push((
200 (*isd_as).into(),
201 UnderlayInfo::Snap(underlay.address.clone()),
202 ));
203 }
204 }
205 Ok((underlays, udp_underlay_next_hops))
206}