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, net,
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::{Isd, IsdAsn, SocketAddr},
212    packet::ScionPacketRaw,
213    path::Path,
214};
215use scion_sdk_reqwest_connect_rpc::client::CrpcClientError;
216pub use socket::{PathUnawareUdpScionSocket, RawScionSocket, ScmpScionSocket, UdpScionSocket};
217
218// Re-export the main types from the modules
219pub use self::builder::ScionStackBuilder;
220use crate::{
221    path::{
222        PathStrategy,
223        fetcher::{EndhostApiSegmentFetcher, PathFetcherImpl, traits::SegmentFetcher},
224        manager::{
225            MultiPathManager, MultiPathManagerConfig,
226            traits::{PathWaitError, PathWaitTimeoutError},
227        },
228        policy::PathPolicy,
229        scoring::PathScoring,
230    },
231    scionstack::{
232        scmp_handler::{ScmpErrorHandler, ScmpErrorReceiver, ScmpHandler},
233        socket::SendErrorReceiver,
234    },
235    types::Subscribers,
236};
237
238/// The SCION stack can be used to create path-aware SCION sockets or even Quic over SCION
239/// connections.
240///
241/// The SCION stack abstracts over the underlay stack that is used for the underlying
242/// transport.
243pub struct ScionStack {
244    client: Arc<dyn EndhostApiClient>,
245    underlay: Arc<dyn DynUnderlayStack>,
246    scmp_error_receivers: Subscribers<dyn ScmpErrorReceiver>,
247    send_error_receivers: Subscribers<dyn SendErrorReceiver>,
248}
249
250impl fmt::Debug for ScionStack {
251    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
252        f.debug_struct("ScionStack")
253            .field("client", &"Arc<ConnectRpcClient>")
254            .field("underlay", &"Arc<dyn DynUnderlayStack>")
255            .finish()
256    }
257}
258
259impl ScionStack {
260    pub(crate) fn new(
261        client: Arc<dyn EndhostApiClient>,
262        underlay: Arc<dyn DynUnderlayStack>,
263    ) -> Self {
264        Self {
265            client,
266            underlay,
267            scmp_error_receivers: Subscribers::new(),
268            send_error_receivers: Subscribers::new(),
269        }
270    }
271
272    /// Create a path-aware SCION socket with automatic path management.
273    ///
274    /// # Arguments
275    /// * `bind_addr` - The address to bind the socket to. If None, an available address will be
276    ///   used.
277    ///
278    /// # Returns
279    /// A path-aware SCION socket.
280    pub async fn bind(
281        &self,
282        bind_addr: Option<SocketAddr>,
283    ) -> Result<UdpScionSocket, ScionSocketBindError> {
284        self.bind_with_config(bind_addr, SocketConfig::default())
285            .await
286    }
287
288    /// Create a path-aware SCION socket with custom configuration.
289    ///
290    /// # Arguments
291    /// * `bind_addr` - The address to bind the socket to. If None, an available address will be
292    ///   used.
293    /// * `socket_config` - Configuration for the socket.
294    ///
295    /// # Returns
296    /// A path-aware SCION socket.
297    pub async fn bind_with_config(
298        &self,
299        bind_addr: Option<SocketAddr>,
300        mut socket_config: SocketConfig,
301    ) -> Result<UdpScionSocket, ScionSocketBindError> {
302        let socket = PathUnawareUdpScionSocket::new(
303            self.underlay
304                .bind_socket(SocketKind::Udp, bind_addr)
305                .await?,
306            vec![Box::new(ScmpErrorHandler::new(
307                self.scmp_error_receivers.clone(),
308            ))],
309        );
310
311        if !socket_config.disable_endhost_api_segment_fetcher {
312            let connect_rpc_fetcher: Box<dyn SegmentFetcher> =
313                Box::new(EndhostApiSegmentFetcher::new(self.client.clone()));
314            socket_config.segment_fetchers.push(connect_rpc_fetcher);
315        }
316        let fetcher = PathFetcherImpl::new(socket_config.segment_fetchers);
317
318        // Use default scorers if none are configured.
319        if socket_config.path_strategy.scoring.is_empty() {
320            socket_config.path_strategy.scoring.use_default_scorers();
321        }
322
323        let pather = Arc::new(
324            MultiPathManager::new(
325                MultiPathManagerConfig::default(),
326                fetcher,
327                socket_config.path_strategy,
328            )
329            .expect("should not fail with default configuration"),
330        );
331
332        // Register the path manager as a SCMP error receiver and send error receiver.
333        self.scmp_error_receivers.register(pather.clone());
334        self.send_error_receivers.register(pather.clone());
335
336        Ok(UdpScionSocket::new(
337            socket,
338            pather,
339            socket_config.connect_timeout,
340            self.send_error_receivers.clone(),
341        ))
342    }
343
344    /// Create a connected path-aware SCION socket with automatic path management.
345    ///
346    /// # Arguments
347    /// * `remote_addr` - The remote address to connect to.
348    /// * `bind_addr` - The address to bind the socket to. If None, an available address will be
349    ///   used.
350    ///
351    /// # Returns
352    /// A connected path-aware SCION socket.
353    pub async fn connect(
354        &self,
355        remote_addr: SocketAddr,
356        bind_addr: Option<SocketAddr>,
357    ) -> Result<UdpScionSocket, ScionSocketConnectError> {
358        let socket = self.bind(bind_addr).await?;
359        socket.connect(remote_addr).await
360    }
361
362    /// Create a connected path-aware SCION socket with custom configuration.
363    ///
364    /// # Arguments
365    /// * `remote_addr` - The remote address to connect to.
366    /// * `bind_addr` - The address to bind the socket to. If None, an available address will be
367    ///   used.
368    /// * `socket_config` - Configuration for the socket
369    ///
370    /// # Returns
371    /// A connected path-aware SCION socket.
372    pub async fn connect_with_config(
373        &self,
374        remote_addr: SocketAddr,
375        bind_addr: Option<SocketAddr>,
376        socket_config: SocketConfig,
377    ) -> Result<UdpScionSocket, ScionSocketConnectError> {
378        let socket = self.bind_with_config(bind_addr, socket_config).await?;
379        socket.connect(remote_addr).await
380    }
381
382    /// Create a socket that can send and receive SCMP messages.
383    ///
384    /// # Arguments
385    /// * `bind_addr` - The address to bind the socket to. If None, an available address will be
386    ///   used.
387    ///
388    /// # Returns
389    /// A SCMP socket.
390    pub async fn bind_scmp(
391        &self,
392        bind_addr: Option<SocketAddr>,
393    ) -> Result<ScmpScionSocket, ScionSocketBindError> {
394        let socket = self
395            .underlay
396            .bind_socket(SocketKind::Scmp, bind_addr)
397            .await?;
398        Ok(ScmpScionSocket::new(socket))
399    }
400
401    /// Create a raw SCION socket.
402    /// A raw SCION socket can be used to send and receive raw SCION packets.
403    /// It is still bound to a specific UDP port because this is needed for packets
404    /// 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.
405    ///
406    /// # Arguments
407    /// * `bind_addr` - The address to bind the socket to. If None, an available address will be
408    ///   used.
409    ///
410    /// # Returns
411    /// A raw SCION socket.
412    pub async fn bind_raw(
413        &self,
414        bind_addr: Option<SocketAddr>,
415    ) -> Result<RawScionSocket, ScionSocketBindError> {
416        let socket = self
417            .underlay
418            .bind_socket(SocketKind::Raw, bind_addr)
419            .await?;
420        Ok(RawScionSocket::new(socket))
421    }
422
423    /// Create a path-unaware SCION socket for advanced use cases.
424    ///
425    /// This socket can send and receive datagrams, but requires explicit paths for sending.
426    /// Use this when you need full control over path selection.
427    ///
428    /// # Arguments
429    /// * `bind_addr` - The address to bind the socket to. If None, an available address will be
430    ///   used.
431    ///
432    /// # Returns
433    /// A path-unaware SCION socket.
434    pub async fn bind_path_unaware(
435        &self,
436        bind_addr: Option<SocketAddr>,
437    ) -> Result<PathUnawareUdpScionSocket, ScionSocketBindError> {
438        let socket = self
439            .underlay
440            .bind_socket(SocketKind::Udp, bind_addr)
441            .await?;
442
443        Ok(PathUnawareUdpScionSocket::new(socket, vec![]))
444    }
445
446    /// Create a QUIC over SCION endpoint.
447    ///
448    /// This is a convenience method that creates a QUIC (quinn) endpoint over a SCION socket.
449    ///
450    /// # Arguments
451    /// * `bind_addr` - The address to bind the socket to. If None, an available address will be
452    ///   used.
453    /// * `config` - The quinn endpoint configuration.
454    /// * `server_config` - The quinn server configuration.
455    /// * `runtime` - The runtime to spawn tasks on.
456    ///
457    /// # Returns
458    /// A QUIC endpoint that can be used to accept or create QUIC connections.
459    pub async fn quic_endpoint(
460        &self,
461        bind_addr: Option<SocketAddr>,
462        config: quinn::EndpointConfig,
463        server_config: Option<quinn::ServerConfig>,
464        runtime: Option<Arc<dyn quinn::Runtime>>,
465    ) -> anyhow::Result<Endpoint> {
466        self.quic_endpoint_with_config(
467            bind_addr,
468            config,
469            server_config,
470            runtime,
471            SocketConfig::default(),
472        )
473        .await
474    }
475
476    /// Create a QUIC over SCION endpoint using custom socket configuration.
477    ///
478    /// This is a convenience method that creates a QUIC (quinn) endpoint over a SCION socket.
479    ///
480    /// # Arguments
481    /// * `bind_addr` - The address to bind the socket to. If None, an available address will be
482    ///   used.
483    /// * `config` - The quinn endpoint configuration.
484    /// * `server_config` - The quinn server configuration.
485    /// * `runtime` - The runtime to spawn tasks on.
486    /// * `socket_config` - Scion Socket configuration
487    ///
488    /// # Returns
489    /// A QUIC endpoint that can be used to accept or create QUIC connections.
490    pub async fn quic_endpoint_with_config(
491        &self,
492        bind_addr: Option<SocketAddr>,
493        config: quinn::EndpointConfig,
494        server_config: Option<quinn::ServerConfig>,
495        runtime: Option<Arc<dyn quinn::Runtime>>,
496        socket_config: SocketConfig,
497    ) -> anyhow::Result<Endpoint> {
498        let scmp_handlers: Vec<Box<dyn ScmpHandler>> = vec![Box::new(ScmpErrorHandler::new(
499            self.scmp_error_receivers.clone(),
500        ))];
501        let socket = self
502            .underlay
503            .bind_async_udp_socket(bind_addr, scmp_handlers)
504            .await?;
505        let address_translator = Arc::new(AddressTranslator::default());
506
507        let pather = {
508            let mut segment_fetchers = socket_config.segment_fetchers;
509            if !socket_config.disable_endhost_api_segment_fetcher {
510                let connect_rpc_fetcher: Box<dyn SegmentFetcher> =
511                    Box::new(EndhostApiSegmentFetcher::new(self.client.clone()));
512                segment_fetchers.push(connect_rpc_fetcher);
513            }
514            let fetcher = PathFetcherImpl::new(segment_fetchers);
515
516            // Use default scorers if none are configured.
517            let mut strategy = socket_config.path_strategy;
518            if strategy.scoring.is_empty() {
519                strategy.scoring.use_default_scorers();
520            }
521
522            Arc::new(
523                MultiPathManager::new(MultiPathManagerConfig::default(), fetcher, strategy)
524                    .map_err(|e| anyhow::anyhow!("failed to create path manager: {}", e))?,
525            )
526        };
527
528        // Register the path manager as a SCMP error receiver.
529        self.scmp_error_receivers.register(pather.clone());
530
531        let local_scion_addr = socket.local_addr();
532
533        let socket = Arc::new(ScionAsyncUdpSocket::new(
534            socket,
535            pather.clone(),
536            address_translator.clone(),
537        ));
538
539        let runtime = match runtime {
540            Some(runtime) => runtime,
541            None => quinn::default_runtime().context("No runtime found")?,
542        };
543
544        Ok(Endpoint::new_with_abstract_socket(
545            config,
546            server_config,
547            socket,
548            local_scion_addr,
549            runtime,
550            pather,
551            address_translator,
552        )?)
553    }
554
555    /// Get the list of local ISD-ASes available on the endhost.
556    ///
557    /// # Returns
558    ///
559    /// A list of local ISD-AS identifiers.
560    pub fn local_ases(&self) -> Vec<IsdAsn> {
561        self.underlay.local_ases()
562    }
563
564    /// Creates a path manager with default configuration.
565    pub fn create_path_manager(&self) -> MultiPathManager<PathFetcherImpl> {
566        let fetcher = PathFetcherImpl::new(vec![Box::new(EndhostApiSegmentFetcher::new(
567            self.client.clone(),
568        ))]);
569        let mut strategy = PathStrategy::default();
570
571        strategy.scoring.use_default_scorers();
572
573        MultiPathManager::new(MultiPathManagerConfig::default(), fetcher, strategy)
574            .expect("should not fail with default configuration")
575    }
576}
577
578/// Default timeout for creating a connected socket
579pub const DEFAULT_CONNECT_TIMEOUT: Duration = Duration::from_secs(30);
580
581/// Configuration for a path aware socket.
582#[derive(Default)]
583pub struct SocketConfig {
584    pub(crate) segment_fetchers: Vec<Box<dyn SegmentFetcher>>,
585    pub(crate) disable_endhost_api_segment_fetcher: bool,
586    pub(crate) path_strategy: PathStrategy,
587    pub(crate) connect_timeout: Duration,
588}
589
590impl SocketConfig {
591    /// Creates a new default socket configuration.
592    pub fn new() -> Self {
593        Self {
594            segment_fetchers: Vec::new(),
595            disable_endhost_api_segment_fetcher: false,
596            path_strategy: Default::default(),
597            connect_timeout: DEFAULT_CONNECT_TIMEOUT,
598        }
599    }
600
601    /// Adds a path policy.
602    ///
603    /// Path policies can restrict the set of usable paths based on their characteristics.
604    /// E.g. filtering out paths that go through certain ASes.
605    ///
606    /// See [`HopPatternPolicy`](scion_proto::path::policy::hop_pattern::HopPatternPolicy) and
607    /// [`AclPolicy`](scion_proto::path::policy::acl::AclPolicy)
608    pub fn with_path_policy(mut self, policy: impl PathPolicy) -> Self {
609        self.path_strategy.add_policy(policy);
610        self
611    }
612
613    /// Add a path scoring strategy.
614    ///
615    /// Path scores signal which paths to prioritize based on their characteristics.
616    ///
617    /// `scoring` - The path scoring strategy to add.
618    /// `impact` - The impact weight of the scoring strategy. Higher values increase the influence
619    ///
620    /// If no scoring strategies are added, scoring defaults to preferring shorter and more reliable
621    /// paths.
622    pub fn with_path_scoring(mut self, scoring: impl PathScoring, impact: f32) -> Self {
623        self.path_strategy.scoring = self.path_strategy.scoring.with_scorer(scoring, impact);
624        self
625    }
626
627    /// Sets connection timeout for `connect` functions
628    ///
629    /// Defaults to [DEFAULT_CONNECT_TIMEOUT]
630    pub fn with_connection_timeout(mut self, timeout: Duration) -> Self {
631        self.connect_timeout = timeout;
632        self
633    }
634
635    /// Add an additional segment fetcher.
636    ///
637    /// By default, only path segments retrieved via the endhost API are used. Adding additional
638    /// segment fetchers enables to build paths from different segment sources.
639    pub fn with_segment_fetcher(mut self, fetcher: Box<dyn SegmentFetcher>) -> Self {
640        self.segment_fetchers.push(fetcher);
641        self
642    }
643
644    /// Disable fetching path segments from the endhost API.
645    pub fn disable_endhost_api_segment_fetcher(mut self) -> Self {
646        self.disable_endhost_api_segment_fetcher = true;
647        self
648    }
649}
650
651/// Error return when binding a socket.
652#[derive(Debug, thiserror::Error)]
653pub enum ScionSocketBindError {
654    /// The provided bind address cannot be bound to.
655    /// E.g. because it is not assigned to the endhost or because the address
656    /// type is not supported.
657    #[error(transparent)]
658    InvalidBindAddress(InvalidBindAddressError),
659    /// The provided port is already in use.
660    #[error("port {0} is already in use")]
661    PortAlreadyInUse(u16),
662    /// Failed to connect to SNAP data plane.
663    #[error(transparent)]
664    SnapConnectionError(SnapConnectionError),
665    /// No underlay available to bind the requested address.
666    #[error("underlay unavailable for the requested ISD: {0}")]
667    NoUnderlayAvailable(Isd),
668    /// An error that is not covered by the variants above.
669    #[error("other error: {0}")]
670    Other(#[from] Box<dyn std::error::Error + Send + Sync>),
671}
672
673/// Error related to the bind address of the socket.
674#[derive(Debug, thiserror::Error, PartialEq, Eq)]
675pub enum InvalidBindAddressError {
676    /// The provided bind address is a service address.
677    #[error("cannot bind to service addresses: {0}")]
678    ServiceAddress(SocketAddr),
679    /// The requested bind address cannot be bound to.
680    #[error("cannot bind to requested address: {0}")]
681    CannotBindToRequestedAddress(SocketAddr, Cow<'static, str>),
682    /// The assigned address does not match the requested address.
683    /// This is likely due to NAT.
684    #[error(
685        "assigned address ({assigned_addr}) does not match requested address ({bind_addr}), likely due to NAT"
686    )]
687    AddressMismatch {
688        /// The assigned address.
689        assigned_addr: SocketAddr,
690        /// The requested bind address.
691        bind_addr: SocketAddr,
692    },
693    /// Could not find any local IP address to bind to.
694    #[error("could not find any local IP address to bind to")]
695    NoLocalIpAddressFound,
696}
697
698/// Error related to the connection to the SNAP data plane.
699#[derive(Debug, thiserror::Error)]
700pub enum SnapConnectionError {
701    /// Snap sockets cannot be bound without a SNAP token source.
702    #[error("SNAP token source is missing")]
703    SnapTokenSourceMissing,
704    /// Failed to create the SNAP control plane client.
705    #[error("failed to create SNAP control plane client: {0}")]
706    ControlPlaneClientCreationError(anyhow::Error),
707    /// Failed to discover the SNAP data plane.
708    #[error("failed to discover SNAP data plane: {0}")]
709    DataPlaneDiscoveryError(CrpcClientError),
710    /// Failed to establish the SNAP tunnel.
711    #[error("failed to establish SNAP tunnel: {0}")]
712    ConnectionEstablishmentError(anyhow::Error),
713}
714
715/// Available kinds of SCION sockets.
716#[derive(Hash, Eq, PartialEq, Clone, Debug, Ord, PartialOrd)]
717pub enum SocketKind {
718    /// UDP socket.
719    Udp,
720    /// SCMP socket.
721    Scmp,
722    /// Raw socket.
723    Raw,
724}
725/// A trait that defines the underlay stack.
726///
727/// The underlay stack is the underlying transport layer that is used to send and receive SCION
728/// packets. Sockets returned by the underlay stack have no path management but allow
729/// sending and receiving SCION packets.
730pub(crate) trait UnderlayStack: Send + Sync {
731    type Socket: UnderlaySocket + 'static;
732    type AsyncUdpSocket: AsyncUdpUnderlaySocket + 'static;
733
734    fn bind_socket(
735        &self,
736        kind: SocketKind,
737        bind_addr: Option<SocketAddr>,
738    ) -> BoxFuture<'_, Result<Self::Socket, ScionSocketBindError>>;
739
740    fn bind_async_udp_socket(
741        &self,
742        bind_addr: Option<SocketAddr>,
743        scmp_handlers: Vec<Box<dyn ScmpHandler>>,
744    ) -> BoxFuture<'_, Result<Self::AsyncUdpSocket, ScionSocketBindError>>;
745
746    /// Get the list of local ISD-ASes available on the endhost.
747    fn local_ases(&self) -> Vec<IsdAsn>;
748}
749
750/// Dyn safe trait for an underlay stack.
751pub(crate) trait DynUnderlayStack: Send + Sync {
752    fn bind_socket(
753        &self,
754        kind: SocketKind,
755        bind_addr: Option<SocketAddr>,
756    ) -> BoxFuture<'_, Result<Box<dyn UnderlaySocket>, ScionSocketBindError>>;
757
758    fn bind_async_udp_socket(
759        &self,
760        bind_addr: Option<SocketAddr>,
761        scmp_handlers: Vec<Box<dyn ScmpHandler>>,
762    ) -> BoxFuture<'_, Result<Arc<dyn AsyncUdpUnderlaySocket>, ScionSocketBindError>>;
763
764    fn local_ases(&self) -> Vec<IsdAsn>;
765}
766
767impl<U: UnderlayStack> DynUnderlayStack for U {
768    fn bind_socket(
769        &self,
770        kind: SocketKind,
771        bind_addr: Option<SocketAddr>,
772    ) -> BoxFuture<'_, Result<Box<dyn UnderlaySocket>, ScionSocketBindError>> {
773        Box::pin(async move {
774            let socket = self.bind_socket(kind, bind_addr).await?;
775            Ok(Box::new(socket) as Box<dyn UnderlaySocket>)
776        })
777    }
778
779    fn bind_async_udp_socket(
780        &self,
781        bind_addr: Option<SocketAddr>,
782        scmp_handlers: Vec<Box<dyn ScmpHandler>>,
783    ) -> BoxFuture<'_, Result<Arc<dyn AsyncUdpUnderlaySocket>, ScionSocketBindError>> {
784        Box::pin(async move {
785            let socket = self.bind_async_udp_socket(bind_addr, scmp_handlers).await?;
786            Ok(Arc::new(socket) as Arc<dyn AsyncUdpUnderlaySocket>)
787        })
788    }
789
790    fn local_ases(&self) -> Vec<IsdAsn> {
791        <Self as UnderlayStack>::local_ases(self)
792    }
793}
794
795/// SCION socket connect errors.
796#[derive(Debug, thiserror::Error)]
797pub enum ScionSocketConnectError {
798    /// Could not get a path to the destination
799    #[error("failed to get path to destination: {0}")]
800    PathLookupError(#[from] PathWaitTimeoutError),
801    /// Could not bind the socket
802    #[error(transparent)]
803    BindError(#[from] ScionSocketBindError),
804}
805
806/// SCION socket send errors.
807#[derive(Debug, thiserror::Error)]
808pub enum ScionSocketSendError {
809    /// There was an error looking up the path in the path registry.
810    #[error("path lookup error: {0}")]
811    PathLookupError(#[from] PathWaitError),
812    /// UDP underlay next hop unreachable. This is only
813    /// returned if the selected underlay is UDP.
814    #[error("udp next hop {address:?} unreachable: {isd_as}#{interface_id}: {msg}")]
815    UnderlayNextHopUnreachable {
816        /// ISD-AS of the next hop.
817        isd_as: IsdAsn,
818        /// Interface ID of the next hop.
819        interface_id: u16,
820        /// Address of the next hop, if known.
821        address: Option<net::SocketAddr>,
822        /// Additional message.
823        msg: String,
824    },
825    /// The provided packet is invalid. The underlying socket is
826    /// not able to process the packet.
827    #[error("invalid packet: {0}")]
828    InvalidPacket(Cow<'static, str>),
829    /// The underlying socket is closed.
830    #[error("underlying socket is closed")]
831    Closed,
832    /// IO Error from the underlying connection.
833    #[error("underlying connection returned an I/O error: {0:?}")]
834    IoError(#[from] std::io::Error),
835    /// Error return when send is called on a socket that is not connected.
836    #[error("socket is not connected")]
837    NotConnected,
838}
839
840/// SCION socket receive errors.
841#[derive(Debug, thiserror::Error)]
842pub enum ScionSocketReceiveError {
843    /// Path buffer too small.
844    #[error("provided path buffer is too small (at least 1024 bytes required)")]
845    PathBufTooSmall,
846    /// I/O error.
847    #[error("i/o error: {0:?}")]
848    IoError(#[from] std::io::Error),
849    /// Error return when recv is called on a socket that is not connected.
850    #[error("socket is not connected")]
851    NotConnected,
852}
853
854/// A trait that defines an abstraction over an asynchronous underlay socket.
855/// The socket sends and receives raw SCION packets. Decoding of the next layer
856/// protocol or SCMP handling is left to the caller.
857pub(crate) trait UnderlaySocket: 'static + Send + Sync {
858    /// Send a raw packet. Takes a ScionPacketRaw because it needs to read the path
859    /// to resolve the underlay next hop.
860    fn send<'a>(
861        &'a self,
862        packet: ScionPacketRaw,
863    ) -> BoxFuture<'a, Result<(), ScionSocketSendError>>;
864
865    /// Try to send a raw packet immediately. Takes a ScionPacketRaw because it needs to read the
866    /// path to resolve the underlay next hop.
867    fn try_send(&self, packet: ScionPacketRaw) -> Result<(), ScionSocketSendError>;
868
869    fn recv<'a>(&'a self) -> BoxFuture<'a, Result<ScionPacketRaw, ScionSocketReceiveError>>;
870
871    fn local_addr(&self) -> SocketAddr;
872}
873
874/// A trait that defines an asynchronous path unaware UDP socket.
875/// This can be used to implement the [quinn::AsyncUdpSocket] trait.
876pub(crate) trait AsyncUdpUnderlaySocket: Send + Sync {
877    fn create_io_poller(self: Arc<Self>) -> Pin<Box<dyn udp_polling::UdpPoller>>;
878    /// Try to send a raw SCION UDP packet. Path resolution and packet encoding is
879    /// left to the caller.
880    /// This function should return std::io::ErrorKind::WouldBlock if the packet cannot be sent
881    /// immediately.
882    fn try_send(&self, raw_packet: ScionPacketRaw) -> Result<(), std::io::Error>;
883    /// Poll for receiving a SCION packet with sender and path.
884    /// This function will only return valid UDP packets.
885    /// SCMP packets will be handled internally.
886    fn poll_recv_from_with_path(
887        &self,
888        cx: &mut Context,
889    ) -> Poll<std::io::Result<(SocketAddr, Bytes, Path)>>;
890    fn local_addr(&self) -> SocketAddr;
891}