Skip to content

Commit

Permalink
feat: add SCION SCMP packet definitions
Browse files Browse the repository at this point in the history
  • Loading branch information
mlegner committed Dec 22, 2023
1 parent d792266 commit 836e9e9
Show file tree
Hide file tree
Showing 12 changed files with 344 additions and 36 deletions.
14 changes: 12 additions & 2 deletions crates/scion-proto/src/address/scion_address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use std::net::{Ipv4Addr, Ipv6Addr};

use super::{error::AddressKind, AddressParseError, HostAddr, IsdAsn, ServiceAddr};
use crate::packet::AddressInfo;

/// A SCION network address.
///
Expand All @@ -29,7 +30,7 @@ impl ScionAddr {
}
}

/// Returns the host associated with this socket address.
/// Returns the host associated with this SCION address.
pub const fn host(&self) -> HostAddr {
match self {
ScionAddr::V4(addr) => HostAddr::V4(*addr.host()),
Expand Down Expand Up @@ -60,6 +61,15 @@ impl ScionAddr {
ScionAddr::Svc(addr) => addr.set_isd_asn(new_isd_asn),
}
}

/// Returns the address info corresponding to the SCION address's address type.
pub const fn address_info(&self) -> AddressInfo {
match self {
ScionAddr::V4(_) => AddressInfo::IPV4,
ScionAddr::V6(_) => AddressInfo::IPV6,
ScionAddr::Svc(_) => AddressInfo::SERVICE,
}
}
}

impl AsRef<IsdAsn> for ScionAddr {
Expand Down Expand Up @@ -138,7 +148,7 @@ macro_rules! scion_address {
Self { isd_asn, host }
}

/// Returns the host associated with this socket address.
/// Returns the host associated with this SCION address.
pub const fn host(&self) -> &$host_type {
&self.host
}
Expand Down
9 changes: 9 additions & 0 deletions crates/scion-proto/src/address/socket_address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,15 @@ impl SocketAddr {
}
}

/// Returns the SCION address associated with this socket address.
pub fn scion_address(&self) -> ScionAddr {
match self {
SocketAddr::V4(addr) => ScionAddr::V4(*addr.scion_addr()),
SocketAddr::V6(addr) => ScionAddr::V6(*addr.scion_addr()),
SocketAddr::Svc(addr) => ScionAddr::Svc(*addr.scion_addr()),
}
}

/// Returns the host address associated with this socket address.
pub fn host(&self) -> HostAddr {
match self {
Expand Down
14 changes: 12 additions & 2 deletions crates/scion-proto/src/datagram.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@
use bytes::{Buf, BufMut, Bytes, BytesMut};

use crate::{
packet::{AddressHeader, ByEndpoint, ChecksumDigest, InadequateBufferSize, MessageChecksum},
packet::{
self,
AddressHeader,
ByEndpoint,
ChecksumDigest,
InadequateBufferSize,
MessageChecksum,
},
wire_encoding::{WireDecode, WireEncodeVec},
};

Expand All @@ -14,6 +21,9 @@ pub enum UdpDecodeError {
DatagramEmptyOrTruncated,
#[error("next-header value of SCION header is not correct")]
WrongProtocolNumber(u8),
/// An error when decoding the SCION packet.
#[error(transparent)]
PackedDecodeError(#[from] packet::DecodeError),
}

#[allow(missing_docs)]
Expand All @@ -29,7 +39,7 @@ pub enum UdpEncodeError {
/// the [RFC].
///
/// [RFC]: https://www.ietf.org/archive/id/draft-dekater-scion-dataplane-00.html
#[derive(Debug, Default, PartialEq)]
#[derive(Debug, Default, PartialEq, Clone)]
pub struct UdpMessage {
/// The source and destination ports
pub port: ByEndpoint<u16>,
Expand Down
3 changes: 3 additions & 0 deletions crates/scion-proto/src/packet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ pub use headers::{
pub mod raw;
pub use raw::ScionPacketRaw;

pub mod scmp;
pub use scmp::ScionPacketScmp;

pub mod udp;
pub use udp::ScionPacketUdp;

Expand Down
23 changes: 21 additions & 2 deletions crates/scion-proto/src/packet/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,35 @@ impl From<DataplanePathErrorKind> for DecodeError {
}
}

/// Errors raised when failing to encode a [`super::ScionPacketRaw`] or [`super::ScionPacketUdp`].
#[allow(missing_docs)]
/// Errors raised when failing to encode a [`super::ScionPacketRaw`], [`super::ScionPacketScmp`], or
/// [`super::ScionPacketUdp`].
#[derive(Debug, thiserror::Error, PartialEq, Eq, Clone, Copy)]
pub enum EncodeError {
/// The payload is too large to be properly encoded in a SCION packet.
#[error("packet payload is too large")]
PayloadTooLarge,
/// The overall header is too large.
///
/// This is most likely due to a too long path.
#[error("packet header is too large")]
HeaderTooLarge,
}

/// Errors raised when creating a [`super::ScionPacketScmp`].
#[derive(Debug, thiserror::Error, PartialEq, Eq, Clone, Copy)]
pub enum ScmpEncodeError {
/// Some SCMP messages (notably the [`ScmpTracerouteRequest`][crate::scmp::ScmpTracerouteRequest])
/// require a specific path type.
#[error("the provided path type is not appropriate for this type of packet")]
InappropriatePathType,
/// A provided parameter is out of range.
#[error("a provided parameter is out of range")]
ParameterOutOfRange,
/// A general [`EncodeError`] occurred.
#[error("encoding error")]
GeneralEncodeError(#[from] EncodeError),
}

/// Raised if the buffer does not have sufficient capacity for encoding the SCION headers.
///
/// As the headers can be a maximum of 1020 bytes in length, it is advisable to have at
Expand Down
44 changes: 34 additions & 10 deletions crates/scion-proto/src/packet/headers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ pub use address_header::{AddressHeader, RawHostAddress};

use super::{EncodeError, InadequateBufferSize};
use crate::{
address::SocketAddr,
address::{ScionAddr, SocketAddr},
path::{DataplanePath, Path},
wire_encoding::WireEncode,
};

/// SCION packet headers.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq)]
pub struct ScionHeaders {
/// Metadata about the remaining headers and payload.
pub common: CommonHeader,
Expand All @@ -28,10 +28,10 @@ pub struct ScionHeaders {
}

impl ScionHeaders {
/// Creates a new [`ScionHeaders`] object given the source and destination [`SocketAddr`],
/// the [`Path`], the next-header value, and the payload length
/// Creates a new [`ScionHeaders`] object given the source and destination [`ScionAddr`],
/// the [`Path`], the next-header value, and the payload length.
pub fn new(
endhosts: &ByEndpoint<SocketAddr>,
endhosts: &ByEndpoint<ScionAddr>,
path: &Path,
next_header: u8,
payload_length: usize,
Expand All @@ -51,14 +51,14 @@ impl ScionHeaders {
let common_header = CommonHeader {
version: Version::default(),
traffic_class: 0,
flow_id: Self::simple_flow_id(endhosts),
flow_id: Self::dummy_flow_id(),
next_header,
header_length_factor,
payload_length: payload_length
.try_into()
.map_err(|_| EncodeError::PayloadTooLarge)?,
path_type: path.dataplane_path.path_type(),
address_info: endhosts.map(SocketAddr::address_info),
address_info: endhosts.map(ScionAddr::address_info),
reserved: 0,
};

Expand All @@ -69,10 +69,34 @@ impl ScionHeaders {
})
}

// TODO(mlegner): More sophisticated flow ID?
/// Creates a new [`ScionHeaders`] object given the source and destination [`SocketAddr`],
/// the [`Path`], the next-header value, and the payload length.
pub fn new_with_ports(
endhosts: &ByEndpoint<SocketAddr>,
path: &Path,
next_header: u8,
payload_length: usize,
) -> Result<Self, EncodeError> {
let mut headers = Self::new(
&endhosts.map(SocketAddr::scion_address),
path,
next_header,
payload_length,
)?;
headers.common.flow_id = Self::simple_flow_id_with_ports(endhosts);
Ok(headers)
}

// TODO(mlegner): Figure out better way of setting the flow ID for various cases.

/// Set the flow ID to a dummy value of 1.
const fn dummy_flow_id() -> FlowId {
FlowId::new_unchecked(1)
}

/// Simple flow ID containing the XOR of source and destination port with a prepended 1
/// to prevent a value of 0.
fn simple_flow_id(endhosts: &ByEndpoint<SocketAddr>) -> FlowId {
fn simple_flow_id_with_ports(endhosts: &ByEndpoint<SocketAddr>) -> FlowId {
(0x1_0000 | (endhosts.source.port() ^ endhosts.destination.port()) as u32).into()
}
}
Expand Down Expand Up @@ -160,7 +184,7 @@ mod tests {
source: SocketAddr::from_str("[1-1,10.0.0.1]:10001").unwrap(),
destination: SocketAddr::from_str("[1-2,10.0.0.2]:10002").unwrap(),
};
let headers = ScionHeaders::new(
let headers = ScionHeaders::new_with_ports(
&endpoints,
&Path::empty(endpoints.map(SocketAddr::isd_asn)),
0,
Expand Down
4 changes: 2 additions & 2 deletions crates/scion-proto/src/packet/raw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use crate::{
};

/// A SCION network packet.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq)]
pub struct ScionPacketRaw {
/// Packet headers
pub headers: ScionHeaders,
Expand All @@ -34,7 +34,7 @@ impl ScionPacketRaw {
payload: Bytes,
next_header: u8,
) -> Result<Self, EncodeError> {
let headers = ScionHeaders::new(endhosts, path, next_header, payload.len())?;
let headers = ScionHeaders::new_with_ports(endhosts, path, next_header, payload.len())?;

Ok(Self { headers, payload })
}
Expand Down
138 changes: 138 additions & 0 deletions crates/scion-proto/src/packet/scmp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
//! SCION packets containing SCMP messages.
use bytes::{Buf, Bytes};

use super::{error::ScmpEncodeError, InadequateBufferSize, ScionHeaders, ScionPacketRaw};
use crate::{
address::ScionAddr,
packet::ByEndpoint,
path::{DataplanePath, Path},
scmp::{ScmpDecodeError, ScmpMessage, ScmpTracerouteRequest, ScmpType, SCMP_PROTOCOL_NUMBER},
wire_encoding::{WireDecode, WireEncodeVec},
};

/// A SCION packet containing a SCMP message.
#[derive(Debug, Clone, PartialEq)]
pub struct ScionPacketScmp {
/// Packet headers
pub headers: ScionHeaders,
/// The contained SCMP message
pub message: ScmpMessage,
}

impl ScionPacketScmp {
/// Returns the SCMP message type.
pub fn get_type(&self) -> ScmpType {
self.message.get_type()
}
}

/// The position of a router for a given hop in forwarding direction.
pub enum RouterPosition {
/// The ingress router in forwarding direction.
Ingress,
/// The egress router in forwarding direction.
Egress,
}

impl ScionPacketScmp {
/// Creates a new SCION SCMP packet based on the SCMP message correctly setting the checksum.
///
/// This does not work for an [`ScmpTracerouteRequest`] message, which requires setting specific
/// router alert flags. Use [`Self::new_traceroute_request`] for this purpose.
pub fn new(
endhosts: &ByEndpoint<ScionAddr>,
path: &Path,
mut message: ScmpMessage,
) -> Result<Self, ScmpEncodeError> {
let headers =
ScionHeaders::new(endhosts, path, SCMP_PROTOCOL_NUMBER, message.total_length())?;
message.set_checksum(&headers.address);

Ok(Self { headers, message })
}

/// Creates a new SCION packet containing an [`ScmpTracerouteRequest`].
///
/// This correctly sets the appropriate router alert flags and checksum.
pub fn new_traceroute_request(
endhosts: &ByEndpoint<ScionAddr>,
path: &Path,
identifier: u16,
sequence_number: u16,
hop_index: usize,
router_position: RouterPosition,
) -> Result<Self, ScmpEncodeError> {
let DataplanePath::Standard(ref standard_path) = path.dataplane_path else {
return Err(ScmpEncodeError::InappropriatePathType);
};
let mut standard_path_mut = standard_path.to_mut();
let Some(hop_field) = standard_path_mut.hop_field_mut(hop_index) else {
return Err(ScmpEncodeError::ParameterOutOfRange);
};
match router_position {
RouterPosition::Ingress => hop_field.set_cons_ingress_router_alert(true),
RouterPosition::Egress => hop_field.set_cons_egress_router_alert(true),
};

let path = Path {
dataplane_path: DataplanePath::Standard(standard_path_mut.freeze()),
underlay_next_hop: path.underlay_next_hop,
isd_asn: path.isd_asn,
metadata: None,
};
let packet = Self::new(
endhosts,
&path,
ScmpTracerouteRequest::new(identifier, sequence_number).into(),
)?;

Ok(packet)
}
}

impl TryFrom<ScionPacketRaw> for ScionPacketScmp {
type Error = ScmpDecodeError;

fn try_from(mut value: ScionPacketRaw) -> Result<Self, Self::Error> {
if value.headers.common.next_header != SCMP_PROTOCOL_NUMBER {
return Err(ScmpDecodeError::WrongProtocolNumber(
value.headers.common.next_header,
));
}
Ok(Self {
headers: value.headers,
message: ScmpMessage::decode(&mut value.payload)?,
})
}
}

impl<T: Buf> WireDecode<T> for ScionPacketScmp {
type Error = ScmpDecodeError;

fn decode(data: &mut T) -> Result<Self, Self::Error> {
ScionPacketRaw::decode(data)?.try_into()
}
}

impl WireEncodeVec<3> for ScionPacketScmp {
type Error = InadequateBufferSize;

fn encode_with_unchecked(&self, buffer: &mut bytes::BytesMut) -> [Bytes; 3] {
let encoded_headers = self.headers.encode_with_unchecked(buffer);
let encoded_message = self.message.encode_with_unchecked(buffer);
[
encoded_headers[0].clone(),
encoded_message[0].clone(),
encoded_message[1].clone(),
]
}

fn total_length(&self) -> usize {
self.headers.total_length() + self.message.total_length()
}

fn required_capacity(&self) -> usize {
self.headers.required_capacity() + self.message.required_capacity()
}
}
Loading

0 comments on commit 836e9e9

Please sign in to comment.