Skip to main content

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