scion_stack/scionstack/
socket.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
15use std::sync::Arc;
16
17use bytes::Bytes;
18use chrono::Utc;
19use futures::future::BoxFuture;
20use scion_proto::{
21    address::{ScionAddr, SocketAddr},
22    packet::{ByEndpoint, ScionPacketRaw, ScionPacketScmp, ScionPacketUdp},
23    path::Path,
24    scmp::ScmpMessage,
25};
26use tracing::{debug, trace};
27
28use super::{NetworkError, UnderlaySocket};
29use crate::{
30    path::{
31        Shortest,
32        manager::{CachingPathManager, PathManager, PathWaitError},
33    },
34    scionstack::{ScionSocketReceiveError, ScionSocketSendError},
35};
36
37/// A path unaware UDP SCION socket.
38pub struct PathUnawareUdpScionSocket {
39    inner: Box<dyn UnderlaySocket + Sync + Send>,
40}
41
42impl std::fmt::Debug for PathUnawareUdpScionSocket {
43    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44        f.debug_struct("PathUnawareUdpScionSocket")
45            .field("local_addr", &self.inner.local_addr())
46            .finish()
47    }
48}
49
50impl PathUnawareUdpScionSocket {
51    pub(crate) fn new(socket: Box<dyn UnderlaySocket + Sync + Send>) -> Self {
52        Self { inner: socket }
53    }
54
55    /// Send a SCION UDP datagram via the given path.
56    pub fn send_to_via<'a>(
57        &'a self,
58        payload: &[u8],
59        destination: SocketAddr,
60        path: &Path<&[u8]>,
61    ) -> BoxFuture<'a, Result<(), ScionSocketSendError>> {
62        let packet = match ScionPacketUdp::new(
63            ByEndpoint {
64                source: self.inner.local_addr(),
65                destination,
66            },
67            path.data_plane_path.to_bytes_path(),
68            Bytes::copy_from_slice(payload),
69        ) {
70            Ok(packet) => packet,
71            Err(e) => {
72                return Box::pin(async move {
73                    Err(ScionSocketSendError::InvalidPacket(
74                        format!("Error encoding packet: {e:#}").into(),
75                    ))
76                });
77            }
78        }
79        .into();
80        self.inner.send(packet)
81    }
82
83    /// Receive a SCION packet with the sender and path.
84    #[allow(clippy::type_complexity)]
85    pub fn recv_from_with_path<'a>(
86        &'a self,
87        buffer: &'a mut [u8],
88        path_buffer: &'a mut [u8],
89    ) -> BoxFuture<'a, Result<(usize, SocketAddr, Path<&'a mut [u8]>), ScionSocketReceiveError>>
90    {
91        Box::pin(async move {
92            loop {
93                let packet = self.inner.recv().await?;
94                let packet: ScionPacketUdp = match packet.try_into() {
95                    Ok(packet) => packet,
96                    Err(e) => {
97                        debug!(error = %e, "Received invalid UDP packet, skipping");
98                        continue;
99                    }
100                };
101                let src_addr = match packet.headers.address.source() {
102                    Some(source) => SocketAddr::new(source, packet.src_port()),
103                    None => {
104                        debug!("Received packet without source address header, skipping");
105                        continue;
106                    }
107                };
108                trace!(
109                    "received packet from {}, length {}",
110                    src_addr,
111                    packet.datagram.payload.len()
112                );
113
114                let max_read = std::cmp::min(buffer.len(), packet.datagram.payload.len());
115                buffer[..max_read].copy_from_slice(&packet.datagram.payload[..max_read]);
116
117                if path_buffer.len() < packet.headers.path.raw().len() {
118                    return Err(ScionSocketReceiveError::PathBufTooSmall);
119                }
120
121                let dataplane_path = packet
122                    .headers
123                    .path
124                    .copy_to_slice(&mut path_buffer[..packet.headers.path.raw().len()]);
125
126                // Note, that we do not have the next hop address of the path.
127                // A socket that uses more than one tunnel will need to distinguish between
128                // packets received on different tunnels.
129                let path = Path::new(dataplane_path, packet.headers.address.ia, None);
130
131                return Ok((packet.datagram.payload.len(), src_addr, path));
132            }
133        })
134    }
135
136    /// Receive a SCION packet with the sender.
137    pub fn recv_from<'a>(
138        &'a self,
139        buffer: &'a mut [u8],
140    ) -> BoxFuture<'a, Result<(usize, SocketAddr), ScionSocketReceiveError>> {
141        Box::pin(async move {
142            loop {
143                let packet = self.inner.recv().await?;
144                let packet: ScionPacketUdp = match packet.try_into() {
145                    Ok(packet) => packet,
146                    Err(e) => {
147                        debug!(error = %e, "Received invalid UDP packet, skipping");
148                        continue;
149                    }
150                };
151                let src_addr = match packet.headers.address.source() {
152                    Some(source) => SocketAddr::new(source, packet.src_port()),
153                    None => {
154                        debug!("Received packet without source address header, skipping");
155                        continue;
156                    }
157                };
158
159                trace!(
160                    "received packet from {}, length {}, into buffer of size {}",
161                    src_addr,
162                    packet.datagram.payload.len(),
163                    buffer.len()
164                );
165
166                let max_read = std::cmp::min(buffer.len(), packet.datagram.payload.len());
167                buffer[..max_read].copy_from_slice(&packet.datagram.payload[..max_read]);
168
169                return Ok((packet.datagram.payload.len(), src_addr));
170            }
171        })
172    }
173
174    /// The local address the socket is bound to.
175    fn local_addr(&self) -> SocketAddr {
176        self.inner.local_addr()
177    }
178}
179
180/// A SCMP SCION socket.
181pub struct ScmpScionSocket {
182    inner: Box<dyn UnderlaySocket + Sync + Send>,
183}
184
185impl ScmpScionSocket {
186    pub(crate) fn new(socket: Box<dyn UnderlaySocket + Sync + Send>) -> Self {
187        Self { inner: socket }
188    }
189}
190
191impl ScmpScionSocket {
192    /// Send a SCMP message to the destination via the given path.
193    pub fn send_to_via<'a>(
194        &'a self,
195        message: ScmpMessage,
196        destination: ScionAddr,
197        path: &Path<&[u8]>,
198    ) -> BoxFuture<'a, Result<(), ScionSocketSendError>> {
199        let packet = match ScionPacketScmp::new(
200            ByEndpoint {
201                source: self.inner.local_addr().scion_address(),
202                destination,
203            },
204            path.data_plane_path.to_bytes_path(),
205            message,
206        ) {
207            Ok(packet) => packet,
208            Err(e) => {
209                return Box::pin(async move {
210                    Err(ScionSocketSendError::InvalidPacket(
211                        format!("Error encoding packet: {e:#}").into(),
212                    ))
213                });
214            }
215        };
216        let packet = packet.into();
217        Box::pin(async move { self.inner.send(packet).await })
218    }
219
220    /// Receive a SCMP message with the sender and path.
221    #[allow(clippy::type_complexity)]
222    pub fn recv_from_with_path<'a>(
223        &'a self,
224        path_buffer: &'a mut [u8],
225    ) -> BoxFuture<'a, Result<(ScmpMessage, ScionAddr, Path<&'a mut [u8]>), ScionSocketReceiveError>>
226    {
227        Box::pin(async move {
228            loop {
229                let packet = self.inner.recv().await?;
230                let packet: ScionPacketScmp = match packet.try_into() {
231                    Ok(packet) => packet,
232                    Err(e) => {
233                        debug!(error = %e, "Received invalid SCMP packet, skipping");
234                        continue;
235                    }
236                };
237                let src_addr = match packet.headers.address.source() {
238                    Some(source) => source,
239                    None => {
240                        debug!("Received packet without source address header, skipping");
241                        continue;
242                    }
243                };
244
245                if path_buffer.len() < packet.headers.path.raw().len() {
246                    return Err(ScionSocketReceiveError::PathBufTooSmall);
247                }
248                let dataplane_path = packet
249                    .headers
250                    .path
251                    .copy_to_slice(&mut path_buffer[..packet.headers.path.raw().len()]);
252                let path = Path::new(dataplane_path, packet.headers.address.ia, None);
253
254                return Ok((packet.message, src_addr, path));
255            }
256        })
257    }
258
259    /// Receive a SCMP message with the sender.
260    pub fn recv_from<'a>(
261        &'a self,
262    ) -> BoxFuture<'a, Result<(ScmpMessage, ScionAddr), ScionSocketReceiveError>> {
263        Box::pin(async move {
264            loop {
265                let packet = self.inner.recv().await?;
266                let packet: ScionPacketScmp = match packet.try_into() {
267                    Ok(packet) => packet,
268                    Err(e) => {
269                        debug!(error = %e, "Received invalid SCMP packet, skipping");
270                        continue;
271                    }
272                };
273                let src_addr = match packet.headers.address.source() {
274                    Some(source) => source,
275                    None => {
276                        debug!("Received packet without source address header, skipping");
277                        continue;
278                    }
279                };
280                return Ok((packet.message, src_addr));
281            }
282        })
283    }
284
285    /// Return the local socket address.
286    pub fn local_addr(&self) -> SocketAddr {
287        self.inner.local_addr()
288    }
289}
290
291/// A raw SCION socket.
292pub struct RawScionSocket {
293    inner: Box<dyn UnderlaySocket>,
294}
295
296impl RawScionSocket {
297    pub(crate) fn new(socket: Box<dyn UnderlaySocket + Sync + Send>) -> Self {
298        Self { inner: socket }
299    }
300}
301
302impl RawScionSocket {
303    /// Send a raw SCION packet.
304    pub fn send<'a>(
305        &'a self,
306        packet: ScionPacketRaw,
307    ) -> BoxFuture<'a, Result<(), ScionSocketSendError>> {
308        self.inner.send(packet)
309    }
310
311    /// Receive a raw SCION packet.
312    pub fn recv<'a>(&'a self) -> BoxFuture<'a, Result<ScionPacketRaw, ScionSocketReceiveError>> {
313        self.inner.recv()
314    }
315
316    /// Return the local socket address.
317    pub fn local_addr(&self) -> SocketAddr {
318        self.inner.local_addr()
319    }
320}
321
322/// A path aware UDP socket generic over the underlay socket and path manager.
323pub struct UdpScionSocket<P: PathManager = CachingPathManager<Shortest>> {
324    socket: PathUnawareUdpScionSocket,
325    pather: Arc<P>,
326    remote_addr: Option<SocketAddr>,
327}
328
329impl<P: PathManager> std::fmt::Debug for UdpScionSocket<P> {
330    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
331        f.debug_struct("UdpScionSocket")
332            .field("local_addr", &self.socket.local_addr())
333            .field("remote_addr", &self.remote_addr)
334            .finish()
335    }
336}
337
338impl<P: PathManager> UdpScionSocket<P> {
339    /// Creates a new path aware UDP SCION socket.
340    pub fn new(
341        socket: PathUnawareUdpScionSocket,
342        pather: Arc<P>,
343        remote_addr: Option<SocketAddr>,
344    ) -> Self {
345        Self {
346            socket,
347            pather,
348            remote_addr,
349        }
350    }
351
352    /// Connects the socket to a remote address.
353    pub fn connect(self, remote_addr: SocketAddr) -> Self {
354        Self {
355            remote_addr: Some(remote_addr),
356            ..self
357        }
358    }
359
360    /// Send a datagram to the connected remote address.
361    pub async fn send(&self, payload: &[u8]) -> Result<(), ScionSocketSendError> {
362        if let Some(remote_addr) = self.remote_addr {
363            self.send_to(payload, remote_addr).await
364        } else {
365            Err(ScionSocketSendError::NotConnected)
366        }
367    }
368
369    /// Send a datagram to the specified destination.
370    pub async fn send_to(
371        &self,
372        payload: &[u8],
373        destination: SocketAddr,
374    ) -> Result<(), ScionSocketSendError> {
375        let path = &self
376            .pather
377            .path_wait(
378                self.socket.local_addr().isd_asn(),
379                destination.isd_asn(),
380                Utc::now(),
381            )
382            .await
383            .map_err(|e| {
384                match e {
385                    PathWaitError::FetchFailed(e) => {
386                        ScionSocketSendError::PathLookupError(e.into())
387                    }
388                    PathWaitError::NoPathFound => {
389                        ScionSocketSendError::NetworkUnreachable(
390                            NetworkError::DestinationUnreachable("No path found".to_string()),
391                        )
392                    }
393                }
394            })?;
395        self.socket
396            .send_to_via(payload, destination, &path.to_slice_path())
397            .await
398    }
399
400    /// Send a datagram to the specified destination via the specified path.
401    pub async fn send_to_via(
402        &self,
403        payload: &[u8],
404        destination: SocketAddr,
405        path: &Path<&[u8]>,
406    ) -> Result<(), ScionSocketSendError> {
407        self.socket.send_to_via(payload, destination, path).await
408    }
409
410    /// Receive a datagram from any address, along with the sender address and path.
411    pub async fn recv_from_with_path<'a>(
412        &'a self,
413        buffer: &'a mut [u8],
414        path_buffer: &'a mut [u8],
415    ) -> Result<(usize, SocketAddr, Path<&'a mut [u8]>), ScionSocketReceiveError> {
416        let (len, sender_addr, path): (usize, SocketAddr, Path<&mut [u8]>) =
417            self.socket.recv_from_with_path(buffer, path_buffer).await?;
418
419        match path.to_reversed() {
420            Ok(reversed_path) => {
421                // Register the path for future use
422                self.pather.register_path(
423                    self.socket.local_addr().isd_asn(),
424                    sender_addr.isd_asn(),
425                    Utc::now(),
426                    reversed_path,
427                );
428            }
429            Err(e) => {
430                trace!("Failed to reverse path for registration: {e}")
431            }
432        }
433
434        trace!(
435            "Registered reverse path from {} to {}",
436            self.socket.local_addr(),
437            sender_addr
438        );
439
440        Ok((len, sender_addr, path))
441    }
442
443    /// Receive a datagram from the connected remote address and write it into the provided buffer.
444    pub async fn recv_from(
445        &self,
446        buffer: &mut [u8],
447    ) -> Result<(usize, SocketAddr), ScionSocketReceiveError> {
448        // For this method, we need to get the path to register it, but we don't return it
449        let mut path_buffer = [0u8; 1024]; // Temporary buffer for path
450        let (len, sender_addr, _) = self.recv_from_with_path(buffer, &mut path_buffer).await?;
451        Ok((len, sender_addr))
452    }
453
454    /// Receive a datagram from the connected remote address.
455    ///
456    /// Datagrams from other addresses are silently discarded.
457    pub async fn recv(&self, buffer: &mut [u8]) -> Result<usize, ScionSocketReceiveError> {
458        if self.remote_addr.is_none() {
459            return Err(ScionSocketReceiveError::NotConnected);
460        }
461        loop {
462            let (len, sender_addr) = self.recv_from(buffer).await?;
463            match self.remote_addr {
464                Some(remote_addr) => {
465                    if sender_addr == remote_addr {
466                        return Ok(len);
467                    }
468                }
469                None => return Err(ScionSocketReceiveError::NotConnected),
470            }
471        }
472    }
473
474    /// Returns the local socket address.
475    pub fn local_addr(&self) -> SocketAddr {
476        self.socket.local_addr()
477    }
478}