Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add datagram trait and path-aware datagram #106

Merged
merged 6 commits into from
Dec 20, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions crates/scion-proto/src/address/scion_address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,16 @@ impl ScionAddr {
}
}

impl AsRef<IsdAsn> for ScionAddr {
fn as_ref(&self) -> &IsdAsn {
match self {
ScionAddr::V4(addr) => addr.as_ref(),
ScionAddr::V6(addr) => addr.as_ref(),
ScionAddr::Svc(addr) => addr.as_ref(),
}
}
}

macro_rules! impl_from {
($base:ty, $variant:expr) => {
impl From<$base> for ScionAddr {
Expand Down Expand Up @@ -155,6 +165,12 @@ macro_rules! scion_address {
}
}

impl AsRef<IsdAsn> for $name {
fn as_ref(&self) -> &IsdAsn {
&self.isd_asn
}
}

impl core::fmt::Display for $name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{},{}", self.isd_asn(), self.host())
Expand Down
16 changes: 16 additions & 0 deletions crates/scion-proto/src/address/socket_address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,16 @@ impl SocketAddr {
}
}

impl AsRef<IsdAsn> for SocketAddr {
fn as_ref(&self) -> &IsdAsn {
match self {
SocketAddr::V4(addr) => addr.as_ref(),
SocketAddr::V6(addr) => addr.as_ref(),
SocketAddr::Svc(addr) => addr.as_ref(),
}
}
}

impl From<SocketAddrV4> for SocketAddr {
/// Converts a [`SocketAddrV4`] into a [`SocketAddr::V4`].
#[inline]
Expand Down Expand Up @@ -244,6 +254,12 @@ macro_rules! socket_address {
Ok(Self {scion_addr, port })
}
}

impl AsRef<IsdAsn> for $name {
fn as_ref(&self) -> &IsdAsn {
self.scion_addr.as_ref()
}
}
};
}

Expand Down
27 changes: 25 additions & 2 deletions crates/scion-proto/src/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//! This module contains types for SCION paths and metadata as well as encoding and decoding
//! functions.

use std::net::SocketAddr;
use std::{net::SocketAddr, ops::Deref};

use bytes::Bytes;
use scion_grpc::daemon::v1 as daemon_grpc;
Expand All @@ -27,7 +27,7 @@ pub mod epic;
pub use epic::EpicAuths;

/// A SCION end-to-end path with optional metadata.
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone)]
pub struct Path<T = Bytes> {
/// The raw bytes to be added as the path header to SCION dataplane packets.
pub dataplane_path: DataplanePath<T>,
Expand Down Expand Up @@ -125,6 +125,29 @@ impl Path<Bytes> {
}
}

impl From<Path<&mut [u8]>> for Path<Bytes> {
fn from(value: Path<&mut [u8]>) -> Self {
Self {
dataplane_path: value.dataplane_path.into(),
underlay_next_hop: value.underlay_next_hop,
isd_asn: value.isd_asn,
metadata: value.metadata,
}
}
}

impl<T> PartialEq for Path<T>
where
T: Deref<Target = [u8]>,
{
fn eq(&self, other: &Self) -> bool {
self.dataplane_path == other.dataplane_path
&& self.underlay_next_hop == other.underlay_next_hop
&& self.isd_asn == other.isd_asn
&& self.metadata == other.metadata
}
}
mlegner marked this conversation as resolved.
Show resolved Hide resolved

#[cfg(test)]
mod tests {
use std::net::{IpAddr, Ipv4Addr};
Expand Down
39 changes: 37 additions & 2 deletions crates/scion-proto/src/path/dataplane.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ impl From<u8> for PathType {
pub struct UnsupportedPathType(pub u8);

/// Dataplane path found in a SCION packet.
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone)]
pub enum DataplanePath<T = Bytes> {
/// The empty path type, used for intra-AS hops.
EmptyPath,
Expand Down Expand Up @@ -131,6 +131,17 @@ where
}
}

/// Reverse the path to the provided slice.
///
/// Unsupported path types are copied to the slice, as is.
pub fn reverse_to_slice<'b>(&self, buffer: &'b mut [u8]) -> DataplanePath<&'b mut [u8]> {
match self {
DataplanePath::EmptyPath => DataplanePath::EmptyPath,
DataplanePath::Standard(path) => DataplanePath::Standard(path.reverse_to_slice(buffer)),
DataplanePath::Unsupported { .. } => self.copy_to_slice(buffer),
}
}

/// Reverses the path.
pub fn to_reversed(&self) -> Result<DataplanePath, UnsupportedPathType> {
match self {
Expand Down Expand Up @@ -188,6 +199,30 @@ impl From<StandardPath> for DataplanePath {
}
}

impl<T, U> PartialEq<DataplanePath<U>> for DataplanePath<T>
where
T: Deref<Target = [u8]>,
U: Deref<Target = [u8]>,
{
fn eq(&self, other: &DataplanePath<U>) -> bool {
match (self, other) {
(Self::Standard(lhs), DataplanePath::Standard(rhs)) => lhs.raw() == rhs.raw(),
(
Self::Unsupported {
path_type: l_path_type,
bytes: l_bytes,
},
DataplanePath::Unsupported {
path_type: r_path_type,
bytes: r_bytes,
},
) => l_path_type == r_path_type && l_bytes.deref() == r_bytes.deref(),
(Self::EmptyPath, DataplanePath::EmptyPath) => true,
_ => false,
}
}
}

impl WireEncode for DataplanePath {
type Error = InadequateBufferSize;

Expand Down Expand Up @@ -275,7 +310,7 @@ mod tests {

#[test]
fn reverse_empty() {
let dataplane_path = DataplanePath::EmptyPath;
let dataplane_path = DataplanePath::<Bytes>::EmptyPath;
let reverse_path = dataplane_path.to_reversed().unwrap();
assert_eq!(dataplane_path, reverse_path);
assert_eq!(reverse_path.to_reversed().unwrap(), dataplane_path);
Expand Down
1 change: 1 addition & 0 deletions crates/scion/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ license = "Apache-2.0"
publish = false

[dependencies]
async-trait = "0.1.74"
bytes = "1.5.0"
chrono = { workspace = true, features = ["clock"] }
scion-grpc = { version = "0.1.0", path = "../scion-grpc" }
Expand Down
1 change: 1 addition & 0 deletions crates/scion/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@

pub mod daemon;
pub mod dispatcher;
pub mod pan;
pub mod udp_socket;
9 changes: 9 additions & 0 deletions crates/scion/src/pan.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
//! Path aware networking socket and services.
mod datagram;
pub use datagram::{AsyncScionDatagram, PathAwareDatagram};

mod path_service;
pub use path_service::AsyncPathService;

mod error;
pub use error::{PathErrorKind, ReceiveError, SendError};
198 changes: 198 additions & 0 deletions crates/scion/src/pan/datagram.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
use std::{io, sync::Arc};

use async_trait;
use bytes::Bytes;
use scion_proto::{address::IsdAsn, path::Path};

use super::{AsyncPathService, ReceiveError, SendError};

/// Interface for sending and receiving datagrams asynchronously on the SCION network.
#[async_trait::async_trait]
pub trait AsyncScionDatagram {
mlegner marked this conversation as resolved.
Show resolved Hide resolved
/// The type of the address used for sending and receiving datagrams.
type Addr: AsRef<IsdAsn> + Sync + Send;

/// Receive a datagram, its sender, and path from the socket.
///
/// The payload of the datagram is written into the provided buffer, which must not be
/// empty. If there is insufficient space in the buffer, excess data may be dropped.
///
/// This function returns the number of bytes in the payload (irrespective of whether any
/// were dropped), the address of the sender, and the SCION [`Path`] over which the packet
/// was received.
///
/// The returned path corresponds to the reversed path observed in the packet for known path
/// types, or a copy of the opaque path data for unknown path types. In either case, the raw
/// data comprising the returned path is written to path_buffer, which must be at least
/// [`DataplanePath::MAX_LEN`][`scion_proto::path::DataplanePath::<Bytes>::MAX_LEN`] bytes in
/// length.
async fn recv_from_with_path<'p>(
&self,
buffer: &mut [u8],
path_buffer: &'p mut [u8],
) -> Result<(usize, Self::Addr, Path<&'p mut [u8]>), ReceiveError>;

/// Receive a datagram and its sender.
///
/// This behaves like [`Self::recv_from_with_path`] but does not return the path over which
/// the packet was received.
///
/// In the case where the path is not needed, this method should be used as the
/// implementation may avoid copying the path.
///
/// See [`Self::recv_from_with_path`] for more information.
async fn recv_from(&self, buffer: &mut [u8]) -> Result<(usize, Self::Addr), ReceiveError>;

/// Receive a datagram and its path from the socket.
///
/// Similar to [`Self::recv_from_with_path`], this receives the datagram into the provided
/// buffer. However, as this does not return any information about the sender, this is
/// primarily used where the sender is already known, such as with connected sockets.
///
/// See [`Self::recv_from_with_path`] for more information.
async fn recv_with_path<'p>(
&self,
buffer: &mut [u8],
path_buffer: &'p mut [u8],
) -> Result<(usize, Path<&'p mut [u8]>), ReceiveError> {
let (len, _, path) = self.recv_from_with_path(buffer, path_buffer).await?;
Ok((len, path))
}

/// Receive a datagram.
///
/// Similar to [`Self::recv_from_with_path`], this receives the datagram into the provided
/// buffer. However, as this does not return any information about the sender, this is
/// primarily used where the sender is already known, such as with connected sockets.
///
/// In the case where neither the path nor the sender is needed, this method should be used
/// instead of [`Self::recv_with_path`] as the implementation may avoid copying the path.
///
/// See [`Self::recv_from_with_path`] for more information.
async fn recv(&self, buffer: &mut [u8]) -> Result<usize, ReceiveError> {
let (len, _) = self.recv_from(buffer).await?;
Ok(len)
}

/// Sends the payload to the specified remote address and using the specified path.
async fn send_to_via(
&self,
payload: Bytes,
destination: Self::Addr,
path: &Path,
) -> Result<(), SendError>;

/// Sends the payload using the specified path.
///
/// This assumes that the underlying socket is aware of the destination, and will
/// return an error if the socket cannot identify the destination.
async fn send_via(&self, payload: Bytes, path: &Path) -> Result<(), SendError>;

/// Returns the remote address of the socket, if any.
fn remote_addr(&self) -> Option<Self::Addr>;
}

/// A SCION path-aware socket.
///
/// This socket wraps an [`AsyncScionDatagram`] and [`AsyncPathService`] and handles providing
/// paths to the socket from the path service and notifying the path service of any observed
/// paths from the network as well as any issues related to the paths.
pub struct PathAwareDatagram<D, P> {
jpcsmith marked this conversation as resolved.
Show resolved Hide resolved
socket: D,
path_service: Arc<P>,
}

impl<D, P> PathAwareDatagram<D, P>
where
D: AsyncScionDatagram + Send + Sync,
P: AsyncPathService + Send + Sync,
{
/// Creates a new `PathAwareDatagram` socket that wraps the provided socket and path service.
pub fn new(socket: D, path_service: Arc<P>) -> Self {
Self {
socket,
path_service,
}
}

/// Changes the path service associated with this socket.
pub fn set_path_service(&mut self, path_service: Arc<P>) {
self.path_service = path_service;
}

/// Returns the path service associated with this socket.
pub fn path_service(&self) -> &P {
&self.path_service
}

/// Send a datagram using [`AsyncScionDatagram::send_to_via`] with a path from the path service.
pub async fn send_to(
&self,
payload: Bytes,
destination: <Self as AsyncScionDatagram>::Addr,
) -> Result<(), SendError> {
let path = self.path_to(*destination.as_ref()).await?;
self.send_to_via(payload, destination, path).await
}

/// Send a datagram using [`AsyncScionDatagram::send_via`] with a path from the path service.
pub async fn send(&self, payload: Bytes) -> Result<(), SendError> {
if let Some(remote_addr) = self.remote_addr() {
let path = self.path_to(*remote_addr.as_ref()).await?;
self.send_via(payload, path).await
jpcsmith marked this conversation as resolved.
Show resolved Hide resolved
} else {
Err(SendError::Io(io::ErrorKind::NotConnected.into()))
}
}

async fn path_to(&self, remote_ia: IsdAsn) -> Result<&Path, SendError> {
self.path_service
.path_to(remote_ia)
.await
.map_err(|_err| todo!("handle path failures"))
jpcsmith marked this conversation as resolved.
Show resolved Hide resolved
}
}

#[async_trait::async_trait]
impl<D, P> AsyncScionDatagram for PathAwareDatagram<D, P>
where
D: AsyncScionDatagram + Send + Sync,
P: AsyncPathService + Send + Sync,
{
type Addr = <D as AsyncScionDatagram>::Addr;

async fn recv_from_with_path<'p>(
&self,
buffer: &mut [u8],
path_buffer: &'p mut [u8],
) -> Result<(usize, Self::Addr, Path<&'p mut [u8]>), ReceiveError> {
self.socket.recv_from_with_path(buffer, path_buffer).await
}

async fn recv_from(&self, buffer: &mut [u8]) -> Result<(usize, Self::Addr), ReceiveError> {
self.socket.recv_from(buffer).await
}

async fn send_to_via(
&self,
payload: Bytes,
destination: Self::Addr,
path: &Path,
) -> Result<(), SendError> {
self.socket.send_to_via(payload, destination, path).await
}

async fn send_via(&self, payload: Bytes, path: &Path) -> Result<(), SendError> {
self.socket.send_via(payload, path).await
}

fn remote_addr(&self) -> Option<Self::Addr> {
self.socket.remote_addr()
}
}

impl<D, P> AsRef<D> for PathAwareDatagram<D, P> {
fn as_ref(&self) -> &D {
&self.socket
}
}
Loading