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}