1use std::{borrow::Cow, net, sync::Arc, time::Duration};
17
18use endhost_api_client::client::CrpcEndhostApiClient;
19pub use scion_sdk_reqwest_connect_rpc::client::CrpcClientError;
21use scion_sdk_reqwest_connect_rpc::token_source::{TokenSource, static_token::StaticTokenSource};
22use scion_sdk_utils::backoff::{BackoffConfig, ExponentialBackoff};
23use url::Url;
24
25use crate::{
26 scionstack::ScionStack,
27 underlays::{
28 SnapSocketConfig, UnderlayStack,
29 discovery::PeriodicUnderlayDiscovery,
30 udp::{LocalIpResolver, TargetAddrLocalIpResolver},
31 },
32};
33
34const DEFAULT_UDP_NEXT_HOP_RESOLVER_FETCH_INTERVAL: Duration = Duration::from_secs(600);
35const DEFAULT_SNAP_TUNNEL_RECONNECT_BACKOFF: BackoffConfig = BackoffConfig {
36 minimum_delay_secs: 0.5,
37 maximum_delay_secs: 10.0,
38 factor: 1.2,
39 jitter_secs: 0.1,
40};
41
42pub struct ScionStackBuilder {
61 endhost_api_url: Url,
62 endhost_api_token_source: Option<Arc<dyn TokenSource>>,
63 auth_token_source: Option<Arc<dyn TokenSource>>,
64 preferred_underlay: PreferredUnderlay,
65 snap: SnapUnderlayConfig,
66 udp: UdpUnderlayConfig,
67}
68
69impl ScionStackBuilder {
70 pub fn new(endhost_api_url: Url) -> Self {
75 Self {
76 endhost_api_url,
77 endhost_api_token_source: None,
78 auth_token_source: None,
79 preferred_underlay: PreferredUnderlay::Udp,
80 snap: SnapUnderlayConfig::default(),
81 udp: UdpUnderlayConfig::default(),
82 }
83 }
84
85 pub fn with_prefer_snap(mut self) -> Self {
87 self.preferred_underlay = PreferredUnderlay::Snap;
88 self
89 }
90
91 pub fn with_prefer_udp(mut self) -> Self {
93 self.preferred_underlay = PreferredUnderlay::Udp;
94 self
95 }
96
97 pub fn with_endhost_api_auth_token_source(mut self, source: impl TokenSource) -> Self {
99 self.endhost_api_token_source = Some(Arc::new(source));
100 self
101 }
102
103 pub fn with_endhost_api_auth_token(mut self, token: String) -> Self {
105 self.endhost_api_token_source = Some(Arc::new(StaticTokenSource::from(token)));
106 self
107 }
108
109 pub fn with_auth_token_source(mut self, source: impl TokenSource) -> Self {
113 self.auth_token_source = Some(Arc::new(source));
114 self
115 }
116
117 pub fn with_auth_token(mut self, token: String) -> Self {
121 self.auth_token_source = Some(Arc::new(StaticTokenSource::from(token)));
122 self
123 }
124
125 pub fn with_snap_underlay_config(mut self, config: SnapUnderlayConfig) -> Self {
127 self.snap = config;
128 self
129 }
130
131 pub fn with_udp_underlay_config(mut self, config: UdpUnderlayConfig) -> Self {
133 self.udp = config;
134 self
135 }
136
137 pub async fn build(self) -> Result<ScionStack, BuildScionStackError> {
143 let ScionStackBuilder {
144 endhost_api_url,
145 endhost_api_token_source,
146 auth_token_source,
147 preferred_underlay,
148 snap,
149 udp,
150 } = self;
151
152 let endhost_api_client = {
153 let mut client = CrpcEndhostApiClient::new(&endhost_api_url)
154 .map_err(BuildScionStackError::EndhostApiClientSetupError)?;
155 if let Some(token_source) = endhost_api_token_source.or(auth_token_source.clone()) {
156 client.use_token_source(token_source);
157 }
158 Arc::new(client)
159 };
160
161 let underlay_discovery = PeriodicUnderlayDiscovery::new(
164 endhost_api_client.clone(),
165 udp.udp_next_hop_resolver_fetch_interval,
166 ExponentialBackoff::new(0.5, 10.0, 2.0, 0.5),
168 )
169 .await
170 .map_err(BuildScionStackError::UnderlayDiscoveryError)?;
171
172 let local_ip_resolver: Arc<dyn LocalIpResolver> = match udp.local_ips {
177 Some(ips) => Arc::new(ips),
178 None => {
179 Arc::new(
180 TargetAddrLocalIpResolver::new(endhost_api_url.clone())
181 .map_err(BuildUdpScionStackError::LocalIpResolutionError)?,
182 )
183 }
184 };
185
186 let underlay_stack = UnderlayStack::new(
187 preferred_underlay,
188 Arc::new(underlay_discovery),
189 local_ip_resolver,
190 SnapSocketConfig {
191 snap_token_source: snap.snap_token_source.or(auth_token_source),
192 reconnect_backoff: ExponentialBackoff::new_from_config(
193 snap.tunnel_reconnect_backoff,
194 ),
195 },
196 );
197
198 Ok(ScionStack::new(
199 endhost_api_client,
200 Arc::new(underlay_stack),
201 ))
202 }
203}
204
205#[derive(thiserror::Error, Debug)]
207pub enum BuildScionStackError {
208 #[error("no underlay available: {0}")]
210 UnderlayUnavailable(Cow<'static, str>),
211 #[error("underlay discovery request error: {0:#}")]
215 UnderlayDiscoveryError(CrpcClientError),
216 #[error("endhost API client setup error: {0:#}")]
218 EndhostApiClientSetupError(anyhow::Error),
219 #[error(transparent)]
222 Snap(#[from] BuildSnapScionStackError),
223 #[error(transparent)]
226 Udp(#[from] BuildUdpScionStackError),
227 #[error("internal error: {0:#}")]
229 Internal(anyhow::Error),
230}
231
232#[derive(thiserror::Error, Debug)]
234pub enum BuildSnapScionStackError {
235 #[error("no SNAP data plane available: {0}")]
237 DataPlaneUnavailable(Cow<'static, str>),
238 #[error("control plane client setup error: {0:#}")]
240 ControlPlaneClientSetupError(anyhow::Error),
241 #[error("data plane discovery request error: {0:#}")]
243 DataPlaneDiscoveryError(CrpcClientError),
244}
245
246#[derive(thiserror::Error, Debug)]
248pub enum BuildUdpScionStackError {
249 #[error("local IP resolution error: {0:#}")]
251 LocalIpResolutionError(anyhow::Error),
252}
253
254pub enum PreferredUnderlay {
256 Snap,
258 Udp,
260}
261
262pub struct SnapUnderlayConfig {
264 snap_token_source: Option<Arc<dyn TokenSource>>,
265 snap_dp_index: usize,
266 tunnel_reconnect_backoff: BackoffConfig,
267}
268
269impl Default for SnapUnderlayConfig {
270 fn default() -> Self {
271 Self {
272 snap_token_source: None,
273 snap_dp_index: 0,
274 tunnel_reconnect_backoff: DEFAULT_SNAP_TUNNEL_RECONNECT_BACKOFF,
275 }
276 }
277}
278
279impl SnapUnderlayConfig {
280 pub fn builder() -> SnapUnderlayConfigBuilder {
282 SnapUnderlayConfigBuilder(Self::default())
283 }
284}
285
286pub struct SnapUnderlayConfigBuilder(SnapUnderlayConfig);
288
289impl SnapUnderlayConfigBuilder {
290 pub fn with_auth_token(mut self, token: String) -> Self {
292 self.0.snap_token_source = Some(Arc::new(StaticTokenSource::from(token)));
293 self
294 }
295
296 pub fn with_auth_token_source(mut self, source: impl TokenSource) -> Self {
298 self.0.snap_token_source = Some(Arc::new(source));
299 self
300 }
301
302 pub fn with_snap_dp_index(mut self, dp_index: usize) -> Self {
308 self.0.snap_dp_index = dp_index;
309 self
310 }
311
312 pub fn with_tunnel_reconnect_backoff(
321 mut self,
322 minimum_delay_secs: Duration,
323 maximum_delay_secs: Duration,
324 factor: f32,
325 jitter_secs: Duration,
326 ) -> Self {
327 self.0.tunnel_reconnect_backoff = BackoffConfig {
328 minimum_delay_secs: minimum_delay_secs.as_secs_f32(),
329 maximum_delay_secs: maximum_delay_secs.as_secs_f32(),
330 factor,
331 jitter_secs: jitter_secs.as_secs_f32(),
332 };
333 self
334 }
335
336 pub fn build(self) -> SnapUnderlayConfig {
342 self.0
343 }
344}
345
346pub struct UdpUnderlayConfig {
348 udp_next_hop_resolver_fetch_interval: Duration,
349 local_ips: Option<Vec<net::IpAddr>>,
350}
351
352impl Default for UdpUnderlayConfig {
353 fn default() -> Self {
354 Self {
355 udp_next_hop_resolver_fetch_interval: DEFAULT_UDP_NEXT_HOP_RESOLVER_FETCH_INTERVAL,
356 local_ips: None,
357 }
358 }
359}
360
361impl UdpUnderlayConfig {
362 pub fn builder() -> UdpUnderlayConfigBuilder {
364 UdpUnderlayConfigBuilder(Self::default())
365 }
366}
367
368pub struct UdpUnderlayConfigBuilder(UdpUnderlayConfig);
370
371impl UdpUnderlayConfigBuilder {
372 pub fn with_local_ips(mut self, local_ips: Vec<net::IpAddr>) -> Self {
375 self.0.local_ips = Some(local_ips);
376 self
377 }
378
379 pub fn with_udp_next_hop_resolver_fetch_interval(mut self, fetch_interval: Duration) -> Self {
382 self.0.udp_next_hop_resolver_fetch_interval = fetch_interval;
383 self
384 }
385
386 pub fn build(self) -> UdpUnderlayConfig {
388 self.0
389 }
390}