scion_stack/scionstack.rs
1// Copyright 2025 Anapaya Systems
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! # The SCION endhost stack.
16//!
17//! [ScionStack] is a stateful object that is the conceptual equivalent of the
18//! TCP/IP-stack found in today's common operating systems. It is meant to be
19//! instantiated once per process.
20//!
21//! ## Basic Usage
22//!
23//! ### Creating a path-aware socket (recommended)
24//!
25//! ```
26//! use scion_proto::address::SocketAddr;
27//! use scion_stack::scionstack::{ScionStack, ScionStackBuilder};
28//! use url::Url;
29//!
30//! # async fn socket_example() -> Result<(), Box<dyn std::error::Error>> {
31//! // Create a SCION stack builder
32//! let control_plane_addr: url::Url = "http://127.0.0.1:1234".parse()?;
33//! let builder = ScionStackBuilder::new().with_auth_token("SNAP token".to_string());
34//!
35//! let scion_stack = builder.build().await?;
36//! let socket = scion_stack.bind(None).await?;
37//!
38//! // Parse destination address
39//! let destination: SocketAddr = "1-ff00:0:111,[192.168.1.1]:8080".parse()?;
40//!
41//! socket.send_to(b"hello", destination).await?;
42//! let mut buffer = [0u8; 1024];
43//! let (len, src) = socket.recv_from(&mut buffer).await?;
44//! println!("Received: {:?} from {:?}", &buffer[..len], src);
45//!
46//! # Ok(())
47//! # }
48//! ```
49//!
50//! ### Creating a connected socket.
51//!
52//! ```
53//! use scion_proto::address::SocketAddr;
54//! use scion_stack::scionstack::{ScionStack, ScionStackBuilder};
55//! use url::Url;
56//!
57//! # async fn connected_socket_example() -> Result<(), Box<dyn std::error::Error>> {
58//! // Create a SCION stack builder
59//! let control_plane_addr: url::Url = "http://127.0.0.1:1234".parse()?;
60//! let builder = ScionStackBuilder::new().with_auth_token("SNAP token".to_string());
61//!
62//! // Parse destination address
63//! let destination: SocketAddr = "1-ff00:0:111,[192.168.1.1]:8080".parse()?;
64//!
65//! let scion_stack = builder.build().await?;
66//! let connected_socket = scion_stack.connect(destination, None).await?;
67//! connected_socket.send(b"hello").await?;
68//! let mut buffer = [0u8; 1024];
69//! let len = connected_socket.recv(&mut buffer).await?;
70//! println!("Received: {:?}", &buffer[..len]);
71//!
72//! # Ok(())
73//! # }
74//! ```
75//!
76//! ### Creating a path-unaware socket
77//!
78//! ```
79//! use scion_proto::{address::SocketAddr, path::Path};
80//! use scion_stack::scionstack::{ScionStack, ScionStackBuilder};
81//! use url::Url;
82//!
83//! # async fn basic_socket_example() -> Result<(), Box<dyn std::error::Error>> {
84//! // Create a SCION stack builder
85//! let control_plane_addr: url::Url = "http://127.0.0.1:1234".parse()?;
86//! let builder = ScionStackBuilder::new().with_auth_token("SNAP token".to_string());
87//!
88//! // Parse addresses
89//! let bind_addr: SocketAddr = "1-ff00:0:110,[127.0.0.1]:8080".parse()?;
90//! let destination: SocketAddr = "1-ff00:0:111,[127.0.0.1]:9090".parse()?;
91//!
92//! // Create a local path for demonstration
93//! let path: scion_proto::path::Path<bytes::Bytes> = Path::local(bind_addr.isd_asn());
94//!
95//! let scion_stack = builder.build().await?;
96//! let socket = scion_stack.bind_path_unaware(Some(bind_addr)).await?;
97//! socket
98//! .send_to_via(b"hello", destination, &path.to_slice_path())
99//! .await?;
100//! let mut buffer = [0u8; 1024];
101//! let (len, sender) = socket.recv_from(&mut buffer).await?;
102//! println!("Received: {:?} from {:?}", &buffer[..len], sender);
103//!
104//! # Ok(())
105//! # }
106//! ```
107//!
108//! ### Resolving SCION TXT records
109//!
110//! ```
111//! use scion_stack::resolver::{ScionDnsResolver, txt::ScionTxtDnsResolver};
112//!
113//! # async fn resolve_example() -> Result<(), Box<dyn std::error::Error>> {
114//! let resolver = ScionTxtDnsResolver::new()?;
115//! let addresses = resolver.resolve("example.com").await?;
116//!
117//! for address in addresses {
118//! println!("Resolved: {}", address);
119//! }
120//!
121//! # Ok(())
122//! # }
123//! ```
124//!
125//! ## Advanced Usage
126//!
127//! ### Custom path selection
128//!
129//! ```
130//! // Implement your own path selection logic
131//! use std::{sync::Arc, time::Duration};
132//!
133//! use bytes::Bytes;
134//! use chrono::{DateTime, Utc};
135//! use scion_proto::{
136//! address::{IsdAsn, SocketAddr},
137//! path::Path,
138//! };
139//! use scion_stack::{
140//! path::manager::traits::PathManager,
141//! scionstack::{ScionStack, ScionStackBuilder, UdpScionSocket},
142//! types::ResFut,
143//! };
144//!
145//! struct MyCustomPathManager;
146//!
147//! impl scion_stack::path::manager::traits::SyncPathManager for MyCustomPathManager {
148//! fn register_path(
149//! &self,
150//! _src: IsdAsn,
151//! _dst: IsdAsn,
152//! _now: DateTime<Utc>,
153//! _path: Path<Bytes>,
154//! ) {
155//! // Optionally implement registration logic
156//! }
157//!
158//! fn try_cached_path(
159//! &self,
160//! _src: IsdAsn,
161//! _dst: IsdAsn,
162//! _now: DateTime<Utc>,
163//! ) -> std::io::Result<Option<Path<Bytes>>> {
164//! todo!()
165//! }
166//! }
167//!
168//! impl scion_stack::path::manager::traits::PathManager for MyCustomPathManager {
169//! fn path_wait(
170//! &self,
171//! src: IsdAsn,
172//! _dst: IsdAsn,
173//! _now: DateTime<Utc>,
174//! ) -> impl ResFut<'_, Path<Bytes>, scion_stack::path::manager::traits::PathWaitError>
175//! {
176//! async move { Ok(Path::local(src)) }
177//! }
178//! }
179//!
180//! # async fn custom_pather_example() -> Result<(), Box<dyn std::error::Error>> {
181//! // Create a SCION stack builder
182//! let control_plane_addr: url::Url = "http://127.0.0.1:1234".parse()?;
183//! let builder = ScionStackBuilder::new()
184//! .with_endhost_api(control_plane_addr)
185//! .with_auth_token("SNAP token".to_string());
186//!
187//! // Parse addresses
188//! let bind_addr: SocketAddr = "1-ff00:0:110,[127.0.0.1]:8080".parse()?;
189//! let destination: SocketAddr = "1-ff00:0:111,[127.0.0.1]:9090".parse()?;
190//!
191//! let scion_stack = builder.build().await?;
192//! let path_unaware_socket = scion_stack.bind_path_unaware(Some(bind_addr)).await?;
193//! let socket = UdpScionSocket::new(
194//! path_unaware_socket,
195//! Arc::new(MyCustomPathManager),
196//! Duration::from_secs(30),
197//! scion_stack::types::Subscribers::new(),
198//! );
199//! socket.send_to(b"hello", destination).await?;
200//!
201//! # Ok(())
202//! # }
203//! ```
204
205pub mod builder;
206pub mod quic;
207pub mod scmp_handler;
208pub mod socket;
209pub(crate) mod udp_polling;
210
211use std::{
212 borrow::Cow,
213 fmt, net,
214 pin::Pin,
215 sync::Arc,
216 task::{Context, Poll},
217 time::Duration,
218};
219
220use anyhow::Context as _;
221use bytes::Bytes;
222use endhost_api_client::client::EndhostApiClient;
223use futures::future::BoxFuture;
224use quic::{AddressTranslator, Endpoint, ScionAsyncUdpSocket};
225use scion_proto::{
226 address::{Isd, IsdAsn, SocketAddr},
227 packet::ScionPacketRaw,
228 path::Path,
229};
230use scion_sdk_reqwest_connect_rpc::client::CrpcClientError;
231use snap_tun::client::ConnectSnapTunSocketError;
232pub use socket::{PathUnawareUdpScionSocket, RawScionSocket, ScmpScionSocket, UdpScionSocket};
233use url::Url;
234
235// Re-export the main types from the modules
236pub use self::builder::ScionStackBuilder;
237use crate::{
238 path::{
239 PathStrategy,
240 fetcher::{EndhostApiSegmentFetcher, PathFetcherImpl, traits::SegmentFetcher},
241 manager::{
242 MultiPathManager, MultiPathManagerConfig,
243 traits::{PathWaitError, PathWaitTimeoutError},
244 },
245 policy::PathPolicy,
246 scoring::PathScoring,
247 },
248 scionstack::{
249 scmp_handler::{ScmpErrorHandler, ScmpErrorReceiver, ScmpHandler},
250 socket::SendErrorReceiver,
251 },
252 types::Subscribers,
253};
254
255/// The SCION stack can be used to create path-aware SCION sockets or even Quic over SCION
256/// connections.
257///
258/// The SCION stack abstracts over the underlay stack that is used for the underlying
259/// transport.
260pub struct ScionStack {
261 endhost_api: Option<Url>,
262 client: Arc<dyn EndhostApiClient>,
263 underlay: Arc<dyn DynUnderlayStack>,
264 scmp_error_receivers: Subscribers<dyn ScmpErrorReceiver>,
265 send_error_receivers: Subscribers<dyn SendErrorReceiver>,
266}
267
268impl fmt::Debug for ScionStack {
269 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
270 f.debug_struct("ScionStack")
271 .field("client", &"Arc<ConnectRpcClient>")
272 .field("underlay", &"Arc<dyn DynUnderlayStack>")
273 .finish()
274 }
275}
276
277impl ScionStack {
278 pub(crate) fn new(
279 endhost_api: Option<Url>,
280 client: Arc<dyn EndhostApiClient>,
281 underlay: Arc<dyn DynUnderlayStack>,
282 ) -> Self {
283 Self {
284 endhost_api,
285 client,
286 underlay,
287 scmp_error_receivers: Subscribers::new(),
288 send_error_receivers: Subscribers::new(),
289 }
290 }
291
292 /// Create a path-aware SCION socket with automatic path management.
293 ///
294 /// # Arguments
295 /// * `bind_addr` - The address to bind the socket to. If None, an available address will be
296 /// used.
297 ///
298 /// # Returns
299 /// A path-aware SCION socket.
300 pub async fn bind(
301 &self,
302 bind_addr: Option<SocketAddr>,
303 ) -> Result<UdpScionSocket, ScionSocketBindError> {
304 self.bind_with_config(bind_addr, SocketConfig::default())
305 .await
306 }
307
308 /// Create a path-aware SCION socket with custom configuration.
309 ///
310 /// # Arguments
311 /// * `bind_addr` - The address to bind the socket to. If None, an available address will be
312 /// used.
313 /// * `socket_config` - Configuration for the socket.
314 ///
315 /// # Returns
316 /// A path-aware SCION socket.
317 pub async fn bind_with_config(
318 &self,
319 bind_addr: Option<SocketAddr>,
320 mut socket_config: SocketConfig,
321 ) -> Result<UdpScionSocket, ScionSocketBindError> {
322 let socket = PathUnawareUdpScionSocket::new(
323 self.underlay
324 .bind_socket(SocketKind::Udp, bind_addr)
325 .await?,
326 vec![Box::new(ScmpErrorHandler::new(
327 self.scmp_error_receivers.clone(),
328 ))],
329 );
330
331 if !socket_config.disable_endhost_api_segment_fetcher {
332 let connect_rpc_fetcher: Box<dyn SegmentFetcher> =
333 Box::new(EndhostApiSegmentFetcher::new(self.client.clone()));
334 socket_config
335 .segment_fetchers
336 .push(("Endhost API".into(), connect_rpc_fetcher));
337 }
338 let fetcher = PathFetcherImpl::new(
339 socket_config.segment_fetchers,
340 socket_config.segment_fetcher_timeout,
341 );
342
343 // Use default scorers if none are configured.
344 if socket_config.path_strategy.scoring.is_empty() {
345 socket_config.path_strategy.scoring.use_default_scorers();
346 }
347
348 let pather = Arc::new(
349 MultiPathManager::new(
350 MultiPathManagerConfig::default(),
351 fetcher,
352 socket_config.path_strategy,
353 )
354 .expect("should not fail with default configuration"),
355 );
356
357 // Register the path manager as a SCMP error receiver and send error receiver.
358 self.scmp_error_receivers.register(pather.clone());
359 self.send_error_receivers.register(pather.clone());
360
361 Ok(UdpScionSocket::new(
362 socket,
363 pather,
364 socket_config.connect_timeout,
365 self.send_error_receivers.clone(),
366 ))
367 }
368
369 /// Create a connected path-aware SCION socket with automatic path management.
370 ///
371 /// # Arguments
372 /// * `remote_addr` - The remote address to connect to.
373 /// * `bind_addr` - The address to bind the socket to. If None, an available address will be
374 /// used.
375 ///
376 /// # Returns
377 /// A connected path-aware SCION socket.
378 pub async fn connect(
379 &self,
380 remote_addr: SocketAddr,
381 bind_addr: Option<SocketAddr>,
382 ) -> Result<UdpScionSocket, ScionSocketConnectError> {
383 let socket = self.bind(bind_addr).await?;
384 socket.connect(remote_addr).await
385 }
386
387 /// Create a connected path-aware SCION socket with custom configuration.
388 ///
389 /// # Arguments
390 /// * `remote_addr` - The remote address to connect to.
391 /// * `bind_addr` - The address to bind the socket to. If None, an available address will be
392 /// used.
393 /// * `socket_config` - Configuration for the socket
394 ///
395 /// # Returns
396 /// A connected path-aware SCION socket.
397 pub async fn connect_with_config(
398 &self,
399 remote_addr: SocketAddr,
400 bind_addr: Option<SocketAddr>,
401 socket_config: SocketConfig,
402 ) -> Result<UdpScionSocket, ScionSocketConnectError> {
403 let socket = self.bind_with_config(bind_addr, socket_config).await?;
404 socket.connect(remote_addr).await
405 }
406
407 /// Create a socket that can send and receive SCMP messages.
408 ///
409 /// # Arguments
410 /// * `bind_addr` - The address to bind the socket to. If None, an available address will be
411 /// used.
412 ///
413 /// # Returns
414 /// A SCMP socket.
415 pub async fn bind_scmp(
416 &self,
417 bind_addr: Option<SocketAddr>,
418 ) -> Result<ScmpScionSocket, ScionSocketBindError> {
419 let socket = self
420 .underlay
421 .bind_socket(SocketKind::Scmp, bind_addr)
422 .await?;
423 Ok(ScmpScionSocket::new(socket))
424 }
425
426 /// Create a raw SCION socket.
427 /// A raw SCION socket can be used to send and receive raw SCION packets.
428 /// It is still bound to a specific UDP port because this is needed for packets
429 /// to be routed in a dispatcherless autonomous system. See <https://docs.scion.org/en/latest/dev/design/router-port-dispatch.html> for a more detailed explanation.
430 ///
431 /// # Arguments
432 /// * `bind_addr` - The address to bind the socket to. If None, an available address will be
433 /// used.
434 ///
435 /// # Returns
436 /// A raw SCION socket.
437 pub async fn bind_raw(
438 &self,
439 bind_addr: Option<SocketAddr>,
440 ) -> Result<RawScionSocket, ScionSocketBindError> {
441 let socket = self
442 .underlay
443 .bind_socket(SocketKind::Raw, bind_addr)
444 .await?;
445 Ok(RawScionSocket::new(socket))
446 }
447
448 /// Create a path-unaware SCION socket for advanced use cases.
449 ///
450 /// This socket can send and receive datagrams, but requires explicit paths for sending.
451 /// Use this when you need full control over path selection.
452 ///
453 /// # Arguments
454 /// * `bind_addr` - The address to bind the socket to. If None, an available address will be
455 /// used.
456 ///
457 /// # Returns
458 /// A path-unaware SCION socket.
459 pub async fn bind_path_unaware(
460 &self,
461 bind_addr: Option<SocketAddr>,
462 ) -> Result<PathUnawareUdpScionSocket, ScionSocketBindError> {
463 let socket = self
464 .underlay
465 .bind_socket(SocketKind::Udp, bind_addr)
466 .await?;
467
468 Ok(PathUnawareUdpScionSocket::new(socket, vec![]))
469 }
470
471 /// Create a QUIC over SCION endpoint.
472 ///
473 /// This is a convenience method that creates a QUIC (quinn) endpoint over a SCION socket.
474 ///
475 /// # Arguments
476 /// * `bind_addr` - The address to bind the socket to. If None, an available address will be
477 /// used.
478 /// * `config` - The quinn endpoint configuration.
479 /// * `server_config` - The quinn server configuration.
480 /// * `runtime` - The runtime to spawn tasks on.
481 ///
482 /// # Returns
483 /// A QUIC endpoint that can be used to accept or create QUIC connections.
484 #[deprecated(
485 since = "0.4.0",
486 note = "will soon be removed; use the quic-scion crate instead"
487 )]
488 pub async fn quic_endpoint(
489 &self,
490 bind_addr: Option<SocketAddr>,
491 config: anapaya_quinn::EndpointConfig,
492 server_config: Option<anapaya_quinn::ServerConfig>,
493 runtime: Option<Arc<dyn anapaya_quinn::Runtime>>,
494 ) -> anyhow::Result<Endpoint> {
495 #[allow(deprecated)]
496 self.quic_endpoint_with_config(
497 bind_addr,
498 config,
499 server_config,
500 runtime,
501 SocketConfig::default(),
502 )
503 .await
504 }
505
506 /// Create a QUIC over SCION endpoint using custom socket configuration.
507 ///
508 /// This is a convenience method that creates a QUIC (quinn) endpoint over a SCION socket.
509 ///
510 /// # Arguments
511 /// * `bind_addr` - The address to bind the socket to. If None, an available address will be
512 /// used.
513 /// * `config` - The quinn endpoint configuration.
514 /// * `server_config` - The quinn server configuration.
515 /// * `runtime` - The runtime to spawn tasks on.
516 /// * `socket_config` - Scion Socket configuration
517 ///
518 /// # Returns
519 /// A QUIC endpoint that can be used to accept or create QUIC connections.
520 #[deprecated(
521 since = "0.4.0",
522 note = "will soon be removed; use the quic-scion crate instead"
523 )]
524 pub async fn quic_endpoint_with_config(
525 &self,
526 bind_addr: Option<SocketAddr>,
527 config: anapaya_quinn::EndpointConfig,
528 server_config: Option<anapaya_quinn::ServerConfig>,
529 runtime: Option<Arc<dyn anapaya_quinn::Runtime>>,
530 socket_config: SocketConfig,
531 ) -> anyhow::Result<Endpoint> {
532 let scmp_handlers: Vec<Box<dyn ScmpHandler>> = vec![Box::new(ScmpErrorHandler::new(
533 self.scmp_error_receivers.clone(),
534 ))];
535 let socket = self
536 .underlay
537 .bind_async_udp_socket(bind_addr, scmp_handlers)
538 .await?;
539 let address_translator = Arc::new(AddressTranslator::default());
540
541 let pather = {
542 let mut segment_fetchers = socket_config.segment_fetchers;
543 if !socket_config.disable_endhost_api_segment_fetcher {
544 let connect_rpc_fetcher: Box<dyn SegmentFetcher> =
545 Box::new(EndhostApiSegmentFetcher::new(self.client.clone()));
546 segment_fetchers.push(("Endhost API".into(), connect_rpc_fetcher));
547 }
548 let fetcher =
549 PathFetcherImpl::new(segment_fetchers, socket_config.segment_fetcher_timeout);
550
551 // Use default scorers if none are configured.
552 let mut strategy = socket_config.path_strategy;
553 if strategy.scoring.is_empty() {
554 strategy.scoring.use_default_scorers();
555 }
556
557 Arc::new(
558 MultiPathManager::new(MultiPathManagerConfig::default(), fetcher, strategy)
559 .map_err(|e| anyhow::anyhow!("failed to create path manager: {}", e))?,
560 )
561 };
562
563 // Register the path manager as a SCMP error receiver.
564 self.scmp_error_receivers.register(pather.clone());
565
566 let local_scion_addr = socket.local_addr();
567
568 let socket = Arc::new(ScionAsyncUdpSocket::new(
569 socket,
570 pather.clone(),
571 address_translator.clone(),
572 ));
573
574 let runtime = match runtime {
575 Some(runtime) => runtime,
576 None => anapaya_quinn::default_runtime().context("No runtime found")?,
577 };
578
579 Ok(Endpoint::new_with_abstract_socket(
580 config,
581 server_config,
582 socket,
583 local_scion_addr,
584 runtime,
585 pather,
586 address_translator,
587 )?)
588 }
589
590 /// Get the list of local ISD-ASes available on the endhost.
591 ///
592 /// # Returns
593 ///
594 /// A list of local ISD-AS identifiers.
595 pub fn local_ases(&self) -> Vec<IsdAsn> {
596 self.underlay.local_ases()
597 }
598
599 /// Get the currently selected endhost API URL, if any.
600 pub fn endhost_api(&self) -> Option<Url> {
601 self.endhost_api.clone()
602 }
603
604 /// Creates a path manager with default configuration.
605 pub fn create_path_manager(&self) -> MultiPathManager<PathFetcherImpl> {
606 let fetcher = PathFetcherImpl::new(
607 vec![(
608 "Endhost API".into(),
609 Box::new(EndhostApiSegmentFetcher::new(self.client.clone())),
610 )],
611 DEFAULT_SEGMENT_FETCHER_TIMEOUT,
612 );
613 let mut strategy = PathStrategy::default();
614
615 strategy.scoring.use_default_scorers();
616
617 MultiPathManager::new(MultiPathManagerConfig::default(), fetcher, strategy)
618 .expect("should not fail with default configuration")
619 }
620}
621
622/// Default timeout for creating a connected socket
623pub const DEFAULT_CONNECT_TIMEOUT: Duration = Duration::from_secs(30);
624
625/// Default timeout for segment fetchers to avoid waiting indefinitely for slow or unresponsive
626/// fetchers.
627pub const DEFAULT_SEGMENT_FETCHER_TIMEOUT: Duration = Duration::from_secs(60);
628
629/// Configuration for a path aware socket.
630pub struct SocketConfig {
631 pub(crate) segment_fetchers: Vec<(String, Box<dyn SegmentFetcher>)>,
632 pub(crate) segment_fetcher_timeout: Duration,
633 pub(crate) disable_endhost_api_segment_fetcher: bool,
634 pub(crate) path_strategy: PathStrategy,
635 pub(crate) connect_timeout: Duration,
636}
637
638impl Default for SocketConfig {
639 fn default() -> Self {
640 Self::new()
641 }
642}
643
644impl SocketConfig {
645 /// Creates a new default socket configuration.
646 pub fn new() -> Self {
647 Self {
648 segment_fetchers: Vec::new(),
649 segment_fetcher_timeout: DEFAULT_SEGMENT_FETCHER_TIMEOUT,
650 disable_endhost_api_segment_fetcher: false,
651 path_strategy: Default::default(),
652 connect_timeout: DEFAULT_CONNECT_TIMEOUT,
653 }
654 }
655
656 /// Adds a path policy.
657 ///
658 /// Path policies can restrict the set of usable paths based on their characteristics.
659 /// E.g. filtering out paths that go through certain ASes.
660 ///
661 /// See [`HopPatternPolicy`](scion_proto::path::policy::hop_pattern::HopPatternPolicy) and
662 /// [`AclPolicy`](scion_proto::path::policy::acl::AclPolicy)
663 pub fn with_path_policy(mut self, policy: impl PathPolicy) -> Self {
664 self.path_strategy.add_policy(policy);
665 self
666 }
667
668 /// Add a path scoring strategy.
669 ///
670 /// Path scores signal which paths to prioritize based on their characteristics.
671 ///
672 /// `scoring` - The path scoring strategy to add.
673 /// `impact` - The impact weight of the scoring strategy. Higher values increase the influence
674 ///
675 /// If no scoring strategies are added, scoring defaults to preferring shorter and more reliable
676 /// paths.
677 pub fn with_path_scoring(mut self, scoring: impl PathScoring, impact: f32) -> Self {
678 self.path_strategy.scoring = self.path_strategy.scoring.with_scorer(scoring, impact);
679 self
680 }
681
682 /// Sets connection timeout for `connect` functions
683 ///
684 /// Defaults to [DEFAULT_CONNECT_TIMEOUT]
685 pub fn with_connection_timeout(mut self, timeout: Duration) -> Self {
686 self.connect_timeout = timeout;
687 self
688 }
689
690 /// Add an additional segment fetcher.
691 ///
692 /// By default, only path segments retrieved via the endhost API are used. Adding additional
693 /// segment fetchers enables to build paths from different segment sources.
694 pub fn with_segment_fetcher(mut self, name: String, fetcher: Box<dyn SegmentFetcher>) -> Self {
695 self.segment_fetchers.push((name, fetcher));
696 self
697 }
698
699 /// Disable fetching path segments from the endhost API.
700 pub fn disable_endhost_api_segment_fetcher(mut self) -> Self {
701 self.disable_endhost_api_segment_fetcher = true;
702 self
703 }
704
705 /// Sets the segment fetcher timeout. The timeout prevents waiting indefinitely for slow or
706 /// unresponsive segment fetchers. If a fetcher does not respond within the timeout, it will be
707 /// skipped for the current path lookup.
708 ///
709 /// Defaults to [DEFAULT_SEGMENT_FETCHER_TIMEOUT].
710 pub fn with_segment_fetcher_timeout(mut self, timeout: Duration) -> Self {
711 self.segment_fetcher_timeout = timeout;
712 self
713 }
714}
715
716/// Error return when binding a socket.
717#[derive(Debug, thiserror::Error)]
718pub enum ScionSocketBindError {
719 /// The provided bind address cannot be bound to.
720 /// E.g. because it is not assigned to the endhost or because the address
721 /// type is not supported.
722 #[error(transparent)]
723 InvalidBindAddress(InvalidBindAddressError),
724 /// The provided port is already in use.
725 #[error("port {0} is already in use")]
726 PortAlreadyInUse(u16),
727 /// Failed to connect to SNAP data plane.
728 #[error(transparent)]
729 SnapConnectionError(SnapConnectionError),
730 /// No underlay available to bind the requested address.
731 #[error("underlay unavailable for the requested ISD: {0}")]
732 NoUnderlayAvailable(Isd),
733 /// An error that is not covered by the variants above.
734 #[error("other error: {0}")]
735 Other(#[from] Box<dyn std::error::Error + Send + Sync>),
736}
737
738/// Error related to the bind address of the socket.
739#[derive(Debug, thiserror::Error, PartialEq, Eq)]
740pub enum InvalidBindAddressError {
741 /// The provided bind address is a service address.
742 #[error("cannot bind to service addresses: {0}")]
743 ServiceAddress(SocketAddr),
744 /// The requested bind address cannot be bound to.
745 #[error("cannot bind to requested address: {0}")]
746 CannotBindToRequestedAddress(SocketAddr, Cow<'static, str>),
747 /// The assigned address does not match the requested address.
748 /// This is likely due to NAT.
749 #[error(
750 "assigned address ({assigned_addr}) does not match requested address ({bind_addr}), likely due to NAT"
751 )]
752 AddressMismatch {
753 /// The assigned address.
754 assigned_addr: SocketAddr,
755 /// The requested bind address.
756 bind_addr: SocketAddr,
757 },
758 /// Could not find any local IP address to bind to.
759 #[error("could not find any local IP address to bind to")]
760 NoLocalIpAddressFound,
761}
762
763/// Error related to the connection to the SNAP data plane.
764#[derive(Debug, thiserror::Error)]
765pub enum SnapConnectionError {
766 /// Snap sockets cannot be bound without a SNAP token source.
767 #[error("SNAP token source is missing")]
768 SnapTokenSourceMissing,
769 /// Error establishing the SNAP tunnel.
770 #[error("error establishing SNAP tunnel: {0}")]
771 TunnelEstablishmentError(#[from] ConnectSnapTunSocketError),
772 /// Failed to create the SNAP control plane client.
773 #[error("failed to create SNAP control plane client: {0}")]
774 ControlPlaneClientCreationError(anyhow::Error),
775 /// Failed to discover the SNAP data plane.
776 #[error("failed to discover SNAP data plane: {0}")]
777 DataPlaneDiscoveryError(CrpcClientError),
778}
779
780/// Available kinds of SCION sockets.
781#[derive(Hash, Eq, PartialEq, Clone, Debug, Ord, PartialOrd)]
782pub enum SocketKind {
783 /// UDP socket.
784 Udp,
785 /// SCMP socket.
786 Scmp,
787 /// Raw socket.
788 Raw,
789}
790/// A trait that defines the underlay stack.
791///
792/// The underlay stack is the underlying transport layer that is used to send and receive SCION
793/// packets. Sockets returned by the underlay stack have no path management but allow
794/// sending and receiving SCION packets.
795pub(crate) trait UnderlayStack: Send + Sync {
796 type Socket: UnderlaySocket + 'static;
797 type AsyncUdpSocket: AsyncUdpUnderlaySocket + 'static;
798
799 fn bind_socket(
800 &self,
801 kind: SocketKind,
802 bind_addr: Option<SocketAddr>,
803 ) -> BoxFuture<'_, Result<Self::Socket, ScionSocketBindError>>;
804
805 fn bind_async_udp_socket(
806 &self,
807 bind_addr: Option<SocketAddr>,
808 scmp_handlers: Vec<Box<dyn ScmpHandler>>,
809 ) -> BoxFuture<'_, Result<Self::AsyncUdpSocket, ScionSocketBindError>>;
810
811 /// Get the list of local ISD-ASes available on the endhost.
812 fn local_ases(&self) -> Vec<IsdAsn>;
813}
814
815/// Dyn safe trait for an underlay stack.
816pub(crate) trait DynUnderlayStack: Send + Sync {
817 fn bind_socket(
818 &self,
819 kind: SocketKind,
820 bind_addr: Option<SocketAddr>,
821 ) -> BoxFuture<'_, Result<Box<dyn UnderlaySocket>, ScionSocketBindError>>;
822
823 fn bind_async_udp_socket(
824 &self,
825 bind_addr: Option<SocketAddr>,
826 scmp_handlers: Vec<Box<dyn ScmpHandler>>,
827 ) -> BoxFuture<'_, Result<Arc<dyn AsyncUdpUnderlaySocket>, ScionSocketBindError>>;
828
829 fn local_ases(&self) -> Vec<IsdAsn>;
830}
831
832impl<U: UnderlayStack> DynUnderlayStack for U {
833 fn bind_socket(
834 &self,
835 kind: SocketKind,
836 bind_addr: Option<SocketAddr>,
837 ) -> BoxFuture<'_, Result<Box<dyn UnderlaySocket>, ScionSocketBindError>> {
838 Box::pin(async move {
839 let socket = self.bind_socket(kind, bind_addr).await?;
840 Ok(Box::new(socket) as Box<dyn UnderlaySocket>)
841 })
842 }
843
844 fn bind_async_udp_socket(
845 &self,
846 bind_addr: Option<SocketAddr>,
847 scmp_handlers: Vec<Box<dyn ScmpHandler>>,
848 ) -> BoxFuture<'_, Result<Arc<dyn AsyncUdpUnderlaySocket>, ScionSocketBindError>> {
849 Box::pin(async move {
850 let socket = self.bind_async_udp_socket(bind_addr, scmp_handlers).await?;
851 Ok(Arc::new(socket) as Arc<dyn AsyncUdpUnderlaySocket>)
852 })
853 }
854
855 fn local_ases(&self) -> Vec<IsdAsn> {
856 <Self as UnderlayStack>::local_ases(self)
857 }
858}
859
860/// SCION socket connect errors.
861#[derive(Debug, thiserror::Error)]
862pub enum ScionSocketConnectError {
863 /// Could not get a path to the destination
864 #[error("failed to get path to destination: {0}")]
865 PathLookupError(#[from] PathWaitTimeoutError),
866 /// Could not bind the socket
867 #[error(transparent)]
868 BindError(#[from] ScionSocketBindError),
869}
870
871/// SCION socket send errors.
872#[derive(Debug, thiserror::Error)]
873pub enum ScionSocketSendError {
874 /// There was an error looking up the path in the path registry.
875 #[error("path lookup error: {0}")]
876 PathLookupError(#[from] PathWaitError),
877 /// UDP underlay next hop unreachable. This is only
878 /// returned if the selected underlay is UDP.
879 #[error("udp next hop {address:?} unreachable: {isd_as}#{interface_id}: {msg}")]
880 UnderlayNextHopUnreachable {
881 /// ISD-AS of the next hop.
882 isd_as: IsdAsn,
883 /// Interface ID of the next hop.
884 interface_id: u16,
885 /// Address of the next hop, if known.
886 address: Option<net::SocketAddr>,
887 /// Additional message.
888 msg: String,
889 },
890 /// The provided packet is invalid. The underlying socket is
891 /// not able to process the packet.
892 #[error("invalid packet: {0}")]
893 InvalidPacket(Cow<'static, str>),
894 /// The underlying socket is closed.
895 #[error("underlying socket is closed")]
896 Closed,
897 /// IO Error from the underlying connection.
898 #[error("underlying connection returned an I/O error: {0:?}")]
899 IoError(#[from] std::io::Error),
900 /// Error return when send is called on a socket that is not connected.
901 #[error("socket is not connected")]
902 NotConnected,
903}
904
905/// Minimum size of the path buffer required by [`ScionSocketReceiveError::PathBufTooSmall`].
906///
907/// This constant can be used to allocate a correctly-sized path buffer when calling
908/// [`UdpScionSocket::recv_from_with_path`](crate::scionstack::UdpScionSocket::recv_from_with_path)
909/// or the corresponding method on [`PathUnawareUdpScionSocket`].
910pub const MIN_PATH_BUFFER_SIZE: usize = 1024;
911
912/// SCION socket receive errors.
913#[derive(Debug, thiserror::Error)]
914pub enum ScionSocketReceiveError {
915 /// Path buffer too small.
916 #[error("provided path buffer is too small (at least {MIN_PATH_BUFFER_SIZE} bytes required)")]
917 PathBufTooSmall,
918 /// I/O error.
919 #[error("i/o error: {0:?}")]
920 IoError(#[from] std::io::Error),
921 /// Error return when recv is called on a socket that is not connected.
922 #[error("socket is not connected")]
923 NotConnected,
924}
925
926/// A trait that defines an abstraction over an asynchronous underlay socket.
927/// The socket sends and receives raw SCION packets. Decoding of the next layer
928/// protocol or SCMP handling is left to the caller.
929pub(crate) trait UnderlaySocket: 'static + Send + Sync {
930 /// Send a raw packet. Takes a ScionPacketRaw because it needs to read the path
931 /// to resolve the underlay next hop.
932 fn send<'a>(
933 &'a self,
934 packet: ScionPacketRaw,
935 ) -> BoxFuture<'a, Result<(), ScionSocketSendError>>;
936
937 /// Try to send a raw packet immediately. Takes a ScionPacketRaw because it needs to read the
938 /// path to resolve the underlay next hop.
939 fn try_send(&self, packet: ScionPacketRaw) -> Result<(), ScionSocketSendError>;
940
941 /// Receive a raw SCION packet.
942 fn recv<'a>(&'a self) -> BoxFuture<'a, Result<ScionPacketRaw, ScionSocketReceiveError>>;
943
944 /// Get the local socket address of this socket.
945 fn local_addr(&self) -> SocketAddr;
946
947 /// The SNAP data plane the socket is connected to (if SNAP underlay is used).
948 fn snap_data_plane(&self) -> Option<net::SocketAddr>;
949}
950
951/// A trait that defines an asynchronous path unaware UDP socket.
952/// This can be used to implement the [anapaya_quinn::AsyncUdpSocket] trait.
953pub(crate) trait AsyncUdpUnderlaySocket: Send + Sync {
954 fn create_io_poller(self: Arc<Self>) -> Pin<Box<dyn udp_polling::UdpPoller>>;
955 /// Try to send a raw SCION UDP packet. Path resolution and packet encoding is
956 /// left to the caller.
957 /// This function should return std::io::ErrorKind::WouldBlock if the packet cannot be sent
958 /// immediately.
959 fn try_send(&self, raw_packet: ScionPacketRaw) -> Result<(), std::io::Error>;
960 /// Poll for receiving a SCION packet with sender and path.
961 /// This function will only return valid UDP packets.
962 /// SCMP packets will be handled internally.
963 fn poll_recv_from_with_path(
964 &self,
965 cx: &mut Context,
966 ) -> Poll<std::io::Result<(SocketAddr, Bytes, Path)>>;
967 /// Get the local socket address of this socket.
968 fn local_addr(&self) -> SocketAddr;
969 /// The SNAP data plane the socket is connected to (if SNAP underlay is used).
970 fn snap_data_plane(&self) -> Option<net::SocketAddr>;
971}
972
973impl Drop for ScionStack {
974 fn drop(&mut self) {
975 tracing::warn!("ScionStack was dropped");
976 }
977}