1use std::sync::Arc;
17
18use scion_proto::address::{IsdAsn, ScionAddr, SocketAddr};
19use scion_sdk_reqwest_connect_rpc::token_source::TokenSource;
20use scion_sdk_utils::backoff::ExponentialBackoff;
21use tokio::net::UdpSocket;
22use url::Url;
23
24use crate::{
25 scionstack::{
26 AsyncUdpUnderlaySocket, DynUnderlayStack, ScionSocketBindError, UnderlaySocket,
27 builder::PreferredUnderlay, scmp_handler::ScmpHandler,
28 },
29 underlays::{
30 discovery::{UnderlayDiscovery, UnderlayInfo},
31 snap::{SnapAsyncUdpSocket, SnapUnderlaySocket},
32 udp::{LocalIpResolver, UdpAsyncUdpUnderlaySocket, UdpUnderlaySocket},
33 },
34};
35
36pub mod discovery;
37pub mod snap;
38pub mod udp;
39
40pub struct SnapSocketConfig {
42 pub snap_token_source: Option<Arc<dyn TokenSource>>,
45 pub reconnect_backoff: ExponentialBackoff,
47}
48
49pub struct UnderlayStack {
51 preferred_underlay: PreferredUnderlay,
52 underlay_discovery: Arc<dyn UnderlayDiscovery>,
53 local_ip_resolver: Arc<dyn LocalIpResolver>,
55 snap_socket_config: SnapSocketConfig,
56}
57
58impl UnderlayStack {
59 pub fn new(
61 preferred_underlay: PreferredUnderlay,
62 underlay_discovery: Arc<dyn UnderlayDiscovery>,
63 local_ip_resolver: Arc<dyn LocalIpResolver>,
64 snap_socket_config: SnapSocketConfig,
65 ) -> Self {
66 Self {
67 preferred_underlay,
68 underlay_discovery,
69 local_ip_resolver,
70 snap_socket_config,
71 }
72 }
73
74 fn select_underlay(&self, requested_isd_as: IsdAsn) -> Option<(IsdAsn, UnderlayInfo)> {
81 let underlays = self.underlay_discovery.underlays(requested_isd_as);
82 match self.preferred_underlay {
83 PreferredUnderlay::Snap => {
84 if let Some(underlay) = underlays
85 .iter()
86 .find(|(_, underlay)| matches!(underlay, UnderlayInfo::Snap(_)))
87 {
88 return Some(underlay.clone());
89 }
90 }
91 PreferredUnderlay::Udp => {
92 if let Some(underlay) = underlays
93 .iter()
94 .find(|(_, underlay)| matches!(underlay, UnderlayInfo::Udp(_)))
95 {
96 return Some(underlay.clone());
97 }
98 }
99 }
100 underlays.into_iter().next()
101 }
102
103 async fn bind_snap_socket(
104 &self,
105 bind_addr: Option<scion_proto::address::SocketAddr>,
106 isd_as: IsdAsn,
107 cp_url: Url,
108 token_source: Option<Arc<dyn TokenSource>>,
109 ) -> Result<SnapUnderlaySocket, ScionSocketBindError> {
110 let token_source = token_source.ok_or(ScionSocketBindError::DataplaneError(
111 "cannot bind SNAP socket without SNAP token (source)".into(),
112 ))?;
113
114 if let Some(SocketAddr::Svc(_)) = bind_addr {
115 return Err(ScionSocketBindError::InvalidBindAddress(
116 bind_addr.unwrap(),
117 "service addresses can't be bound".to_string(),
118 ));
119 }
120
121 let socket = SnapUnderlaySocket::new(
122 isd_as,
123 bind_addr.and_then(|addr| addr.local_address()),
124 cp_url,
125 "localhost".to_string(),
126 self.underlay_discovery.clone(),
127 token_source,
128 self.snap_socket_config.reconnect_backoff,
129 )
130 .await?;
131 Ok(socket)
132 }
133
134 fn resolve_udp_bind_addr(
135 &self,
136 isd_as: IsdAsn,
137 bind_addr: Option<SocketAddr>,
138 ) -> Result<SocketAddr, ScionSocketBindError> {
139 let bind_addr = match bind_addr {
140 Some(addr) => {
141 if addr.is_service() {
142 return Err(ScionSocketBindError::InvalidBindAddress(
143 addr,
144 "service addresses can't be bound".to_string(),
145 ));
146 }
147 addr
148 }
149 None => {
150 let local_address = *self.local_ip_resolver.local_ips().first().ok_or(
151 ScionSocketBindError::UnderlayUnavailable("no local IP address found".into()),
152 )?;
153 SocketAddr::new(ScionAddr::new(isd_as, local_address.into()), 0)
154 }
155 };
156 Ok(bind_addr)
157 }
158
159 async fn bind_udp_socket(
160 &self,
161 isd_as: IsdAsn,
162 bind_addr: Option<SocketAddr>,
163 ) -> Result<(SocketAddr, UdpSocket), ScionSocketBindError> {
164 let bind_addr = self.resolve_udp_bind_addr(isd_as, bind_addr)?;
165 let local_addr =
166 bind_addr
167 .local_address()
168 .ok_or(ScionSocketBindError::InvalidBindAddress(
169 bind_addr,
170 "Service addresses can't be bound".to_string(),
171 ))?;
172 let socket = UdpSocket::bind(local_addr).await.map_err(|e| {
173 match e.kind() {
174 std::io::ErrorKind::AddrInUse => {
175 ScionSocketBindError::PortAlreadyInUse(local_addr.port())
176 }
177 std::io::ErrorKind::AddrNotAvailable | std::io::ErrorKind::InvalidInput => {
178 ScionSocketBindError::InvalidBindAddress(
179 bind_addr,
180 format!("Failed to bind socket: {e:#}"),
181 )
182 }
183 _ => ScionSocketBindError::Other(Box::new(e)),
184 }
185 })?;
186 let local_addr = socket.local_addr().map_err(|e| {
187 ScionSocketBindError::Other(
188 anyhow::anyhow!("failed to get local address: {e}").into_boxed_dyn_error(),
189 )
190 })?;
191 let bind_addr = SocketAddr::new(
192 ScionAddr::new(bind_addr.isd_asn(), local_addr.ip().into()),
193 local_addr.port(),
194 );
195 Ok((bind_addr, socket))
196 }
197}
198
199impl DynUnderlayStack for UnderlayStack {
200 fn bind_socket(
201 &self,
202 _kind: crate::scionstack::SocketKind,
203 bind_addr: Option<scion_proto::address::SocketAddr>,
204 ) -> futures::future::BoxFuture<
205 '_,
206 Result<Box<dyn crate::scionstack::UnderlaySocket>, crate::scionstack::ScionSocketBindError>,
207 > {
208 Box::pin(async move {
209 let requested_isd_as = bind_addr
210 .map(|addr| addr.isd_asn())
211 .unwrap_or(IsdAsn::WILDCARD);
212 match self.select_underlay(requested_isd_as) {
213 Some((isd_as, UnderlayInfo::Snap(cp_url))) => {
214 Ok(Box::new(
215 self.bind_snap_socket(
216 bind_addr,
217 isd_as,
218 cp_url,
219 self.snap_socket_config.snap_token_source.clone(),
220 )
221 .await?,
222 ) as Box<dyn UnderlaySocket>)
223 }
224 Some((isd_as, UnderlayInfo::Udp(_))) => {
225 let (bind_addr, socket) = self.bind_udp_socket(isd_as, bind_addr).await?;
226 Ok(Box::new(UdpUnderlaySocket::new(
227 socket,
228 bind_addr,
229 self.underlay_discovery.clone(),
230 )) as Box<dyn UnderlaySocket>)
231 }
232 None => Err(
233 crate::scionstack::ScionSocketBindError::UnderlayUnavailable(
234 format!(
235 "no underlay available to bind the requested ISD-AS {requested_isd_as}"
236 )
237 .into(),
238 ),
239 ),
240 }
241 })
242 }
243
244 fn bind_async_udp_socket(
245 &self,
246 bind_addr: Option<scion_proto::address::SocketAddr>,
247 scmp_handlers: Vec<Box<dyn ScmpHandler>>,
248 ) -> futures::future::BoxFuture<
249 '_,
250 Result<
251 std::sync::Arc<dyn crate::scionstack::AsyncUdpUnderlaySocket>,
252 crate::scionstack::ScionSocketBindError,
253 >,
254 > {
255 Box::pin(async move {
256 match self.select_underlay(
257 bind_addr
258 .map(|addr| addr.isd_asn())
259 .unwrap_or(IsdAsn::WILDCARD),
260 ) {
261 Some((isd_as, UnderlayInfo::Snap(cp_url))) => {
262 let socket = self
263 .bind_snap_socket(
264 bind_addr,
265 isd_as,
266 cp_url,
267 self.snap_socket_config.snap_token_source.clone(),
268 )
269 .await?;
270 let async_udp_socket = SnapAsyncUdpSocket::new(socket, scmp_handlers);
271 Ok(Arc::new(async_udp_socket) as Arc<dyn AsyncUdpUnderlaySocket + 'static>)
272 }
273 Some((isd_as, UnderlayInfo::Udp(_))) => {
274 let (bind_addr, socket) = self.bind_udp_socket(isd_as, bind_addr).await?;
275 let async_udp_socket = UdpAsyncUdpUnderlaySocket::new(
276 bind_addr,
277 self.underlay_discovery.clone(),
278 socket,
279 scmp_handlers,
280 );
281 Ok(Arc::new(async_udp_socket) as Arc<dyn AsyncUdpUnderlaySocket + 'static>)
282 }
283 None => {
284 Err(
285 crate::scionstack::ScionSocketBindError::UnderlayUnavailable(
286 "no underlay available".into(),
287 ),
288 )
289 }
290 }
291 })
292 }
293
294 fn local_ases(&self) -> Vec<IsdAsn> {
295 let mut isd_ases: Vec<IsdAsn> = self.underlay_discovery.isd_ases().into_iter().collect();
296 isd_ases.sort();
297 isd_ases
298 }
299}