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}