1use std::{net, sync::Arc};
17
18use scion_proto::address::{Isd, IsdAsn, ScionAddr, SocketAddr};
19use scion_sdk_reqwest_connect_rpc::token_source::TokenSource;
20use scion_sdk_utils::backoff::ExponentialBackoff;
21use socket2::{Domain, Protocol, Socket, Type};
22use tokio::net::UdpSocket;
23use url::Url;
24use x25519_dalek::StaticSecret;
25
26use crate::{
27 scionstack::{
28 AsyncUdpUnderlaySocket, DynUnderlayStack, InvalidBindAddressError, ScionSocketBindError,
29 SnapConnectionError, UnderlaySocket, builder::PreferredUnderlay, scmp_handler::ScmpHandler,
30 },
31 underlays::{
32 discovery::{UnderlayDiscovery, UnderlayInfo},
33 udp::{LocalIpResolver, UdpAsyncUdpUnderlaySocket, UdpUnderlaySocket},
34 },
35};
36
37pub mod discovery;
38pub mod snap;
39pub mod udp;
40
41pub struct SnapSocketConfig {
43 pub snap_token_source: Option<Arc<dyn TokenSource>>,
46 pub reconnect_backoff: ExponentialBackoff,
48}
49
50pub struct UnderlayStack {
52 preferred_underlay: PreferredUnderlay,
53 underlay_discovery: Arc<dyn UnderlayDiscovery>,
54 local_ip_resolver: Arc<dyn LocalIpResolver>,
56 snap_socket_config: SnapSocketConfig,
57 snap_static_identity: StaticSecret,
62}
63
64impl UnderlayStack {
65 pub fn new(
67 preferred_underlay: PreferredUnderlay,
68 underlay_discovery: Arc<dyn UnderlayDiscovery>,
69 local_ip_resolver: Arc<dyn LocalIpResolver>,
70 snap_socket_config: SnapSocketConfig,
71 ) -> Self {
72 Self {
73 preferred_underlay,
74 underlay_discovery,
75 local_ip_resolver,
76 snap_socket_config,
77 snap_static_identity: StaticSecret::random(),
78 }
79 }
80
81 fn select_underlay(&self, requested_isd_as: IsdAsn) -> Option<(IsdAsn, UnderlayInfo)> {
88 let underlays = self.underlay_discovery.underlays(requested_isd_as);
89 match self.preferred_underlay {
90 PreferredUnderlay::Snap => {
91 if let Some(underlay) = underlays
92 .iter()
93 .find(|(_, underlay)| matches!(underlay, UnderlayInfo::Snap(_)))
94 {
95 return Some(underlay.clone());
96 }
97 }
98 PreferredUnderlay::Udp => {
99 if let Some(underlay) = underlays
100 .iter()
101 .find(|(_, underlay)| matches!(underlay, UnderlayInfo::Udp(_)))
102 {
103 return Some(underlay.clone());
104 }
105 }
106 }
107 underlays.into_iter().next()
108 }
109
110 async fn bind_snap_socket(
111 &self,
112 bind_addr: Option<scion_proto::address::SocketAddr>,
113 isd_as: IsdAsn,
114 cp_url: Url,
115 token_source: Option<Arc<dyn TokenSource>>,
116 ) -> Result<snap::SnapUnderlaySocket, ScionSocketBindError> {
117 let token_source = token_source.ok_or(ScionSocketBindError::SnapConnectionError(
118 SnapConnectionError::SnapTokenSourceMissing,
119 ))?;
120
121 let local_addr = match bind_addr {
122 Some(addr) => {
123 addr.local_address()
124 .ok_or(ScionSocketBindError::InvalidBindAddress(
125 InvalidBindAddressError::ServiceAddress(addr),
126 ))?
127 }
128 None => "0.0.0.0:0".parse().unwrap(),
129 };
130
131 let bind_addr = SocketAddr::from_std(isd_as, local_addr);
132
133 let udp_socket = bind_udp_underlay_socket(local_addr)?;
134
135 let socket = snap::SnapUnderlaySocket::new(
136 bind_addr,
137 cp_url,
138 udp_socket,
139 token_source,
140 self.snap_static_identity.clone(),
141 1024,
142 )
143 .await?;
144 Ok(socket)
145 }
146
147 fn resolve_udp_bind_addr(
148 &self,
149 isd_as: IsdAsn,
150 bind_addr: Option<SocketAddr>,
151 ) -> Result<SocketAddr, ScionSocketBindError> {
152 let bind_addr = match bind_addr {
153 Some(addr) => {
154 if addr.is_service() {
155 return Err(ScionSocketBindError::InvalidBindAddress(
156 InvalidBindAddressError::ServiceAddress(addr),
157 ));
158 }
159 addr
160 }
161 None => {
162 let local_address = *self.local_ip_resolver.local_ips().first().ok_or(
163 ScionSocketBindError::InvalidBindAddress(
164 InvalidBindAddressError::NoLocalIpAddressFound,
165 ),
166 )?;
167 SocketAddr::new(ScionAddr::new(isd_as, local_address.into()), 0)
168 }
169 };
170 Ok(bind_addr)
171 }
172
173 async fn bind_udp_socket(
174 &self,
175 isd_as: IsdAsn,
176 bind_addr: Option<SocketAddr>,
177 ) -> Result<(SocketAddr, UdpSocket), ScionSocketBindError> {
178 let bind_addr = self.resolve_udp_bind_addr(isd_as, bind_addr)?;
179 let local_addr: net::SocketAddr =
180 bind_addr
181 .local_address()
182 .ok_or(ScionSocketBindError::InvalidBindAddress(
183 InvalidBindAddressError::ServiceAddress(bind_addr),
184 ))?;
185 let socket = bind_udp_underlay_socket(local_addr)?;
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 => {
233 Err(
234 crate::scionstack::ScionSocketBindError::NoUnderlayAvailable(
235 requested_isd_as.isd(),
236 ),
237 )
238 }
239 }
240 })
241 }
242
243 fn bind_async_udp_socket(
244 &self,
245 bind_addr: Option<scion_proto::address::SocketAddr>,
246 scmp_handlers: Vec<Box<dyn ScmpHandler>>,
247 ) -> futures::future::BoxFuture<
248 '_,
249 Result<
250 std::sync::Arc<dyn crate::scionstack::AsyncUdpUnderlaySocket>,
251 crate::scionstack::ScionSocketBindError,
252 >,
253 > {
254 Box::pin(async move {
255 match self.select_underlay(
256 bind_addr
257 .map(|addr| addr.isd_asn())
258 .unwrap_or(IsdAsn::WILDCARD),
259 ) {
260 Some((isd_as, UnderlayInfo::Snap(cp_url))) => {
261 let socket = self
262 .bind_snap_socket(
263 bind_addr,
264 isd_as,
265 cp_url,
266 self.snap_socket_config.snap_token_source.clone(),
267 )
268 .await?;
269 let async_udp_socket = snap::SnapAsyncUdpSocket::new(socket, scmp_handlers);
270 Ok(Arc::new(async_udp_socket) as Arc<dyn AsyncUdpUnderlaySocket + 'static>)
271 }
272 Some((isd_as, UnderlayInfo::Udp(_))) => {
273 let (bind_addr, socket) = self.bind_udp_socket(isd_as, bind_addr).await?;
274 let async_udp_socket = UdpAsyncUdpUnderlaySocket::new(
275 bind_addr,
276 self.underlay_discovery.clone(),
277 socket,
278 scmp_handlers,
279 );
280 Ok(Arc::new(async_udp_socket) as Arc<dyn AsyncUdpUnderlaySocket + 'static>)
281 }
282 None => {
283 Err(
284 crate::scionstack::ScionSocketBindError::NoUnderlayAvailable(
285 bind_addr
286 .map(|addr| addr.isd_asn().isd())
287 .unwrap_or(Isd::WILDCARD),
288 ),
289 )
290 }
291 }
292 })
293 }
294
295 fn local_ases(&self) -> Vec<IsdAsn> {
296 let mut isd_ases: Vec<IsdAsn> = self.underlay_discovery.isd_ases().into_iter().collect();
297 isd_ases.sort();
298 isd_ases
299 }
300}
301
302#[cfg(windows)]
303fn set_exclusive_addr_use(sock: &Socket, enable: bool) -> std::io::Result<()> {
304 use std::{mem, os::windows::io::AsRawSocket};
305
306 use windows_sys::Win32::Networking::WinSock;
307
308 let val: u32 = if enable { 1 } else { 0 };
310
311 let rc = unsafe {
312 WinSock::setsockopt(
313 sock.as_raw_socket() as usize,
314 WinSock::SOL_SOCKET,
315 WinSock::SO_EXCLUSIVEADDRUSE,
316 &val as *const _ as *const _,
317 mem::size_of_val(&val) as _,
318 )
319 };
320
321 if rc == 0 {
322 Ok(())
323 } else {
324 Err(std::io::Error::last_os_error())
325 }
326}
327
328fn bind_udp_underlay_socket(
333 addr: net::SocketAddr,
334) -> Result<tokio::net::UdpSocket, ScionSocketBindError> {
335 let socket = Socket::new(Domain::for_address(addr), Type::DGRAM, Some(Protocol::UDP))
336 .map_err(|e| ScionSocketBindError::Other(Box::new(e)))?;
337 socket
338 .set_nonblocking(true)
339 .map_err(|e| ScionSocketBindError::Other(Box::new(e)))?;
340 if addr.is_ipv6()
341 && let Err(e) = socket.set_only_v6(false)
342 {
343 tracing::debug!(%e, "unable to make socket dual-stack");
344 }
345
346 #[cfg(windows)]
349 set_exclusive_addr_use(&socket, true).map_err(|e| ScionSocketBindError::Other(Box::new(e)))?;
350
351 socket.bind(&addr.into()).map_err(|e| {
352 match e.kind() {
353 std::io::ErrorKind::AddrInUse => ScionSocketBindError::PortAlreadyInUse(addr.port()),
354 std::io::ErrorKind::AddrNotAvailable | std::io::ErrorKind::InvalidInput => {
355 ScionSocketBindError::InvalidBindAddress(
356 InvalidBindAddressError::CannotBindToRequestedAddress(
357 SocketAddr::from_std(IsdAsn::WILDCARD, addr),
358 format!("Failed to bind socket: {e:#}").into(),
359 ),
360 )
361 }
362 #[cfg(windows)]
363 std::io::ErrorKind::PermissionDenied => {
367 ScionSocketBindError::PortAlreadyInUse(addr.port())
368 }
369 _ => ScionSocketBindError::Other(Box::new(e)),
370 }
371 })?;
372
373 tokio::net::UdpSocket::from_std(std::net::UdpSocket::from(socket))
374 .map_err(|e| ScionSocketBindError::Other(Box::new(e)))
375}