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 = 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}