Skip to content

Commit

Permalink
feat: implement standard path reversal
Browse files Browse the repository at this point in the history
  • Loading branch information
mlegner committed Dec 11, 2023
1 parent e5cb4aa commit 52ec651
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 20 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,6 @@ repos:
name: cargo-deny
entry: cargo deny
files: Cargo.(lock|toml)
args: ["--all-features", "check"]
args: ["--all-features", "check", "-D", "warnings"]
language: rust
pass_filenames: false
8 changes: 8 additions & 0 deletions crates/scion-proto/src/packet/headers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,14 @@ pub struct ByEndpoint<T> {
pub destination: T,
}

impl<T> ByEndpoint<T> {
/// Swaps source and destination.
pub fn reverse(&mut self) -> &mut Self {
std::mem::swap(&mut self.source, &mut self.destination);
self
}
}

impl<T: Clone> ByEndpoint<T> {
/// Create a new instance where both the source and destination have the same value.
pub fn with_cloned(source_and_destination: T) -> Self {
Expand Down
7 changes: 7 additions & 0 deletions crates/scion-proto/src/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,13 @@ impl Path {
self.dataplane_path.is_empty()
}

pub fn reverse(&mut self) -> Result<&mut Self, UnsupportedPathType> {
self.dataplane_path.reverse()?;
self.isd_asn.reverse();
self.metadata.as_mut().map(PathMetadata::reverse);
Ok(self)
}

#[tracing::instrument]
pub fn try_from_grpc_with_endpoints(
mut value: daemon_grpc::Path,
Expand Down
12 changes: 12 additions & 0 deletions crates/scion-proto/src/path/dataplane.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,18 @@ impl DataplanePath {
}
}

/// Reverses the path.
pub fn reverse(&mut self) -> Result<&Self, UnsupportedPathType> {
match self {
Self::EmptyPath => (),
Self::Standard(standard_path) => *self = Self::Standard(standard_path.reverse()),
Self::Unsupported { path_type, .. } => {
return Err(UnsupportedPathType(u8::from(*path_type)))
}
}
Ok(self)
}

/// Returns true iff the path is a [`DataplanePath::EmptyPath`]
pub fn is_empty(&self) -> bool {
self == &Self::EmptyPath
Expand Down
27 changes: 27 additions & 0 deletions crates/scion-proto/src/path/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,33 @@ pub struct PathMetadata {
pub epic_auths: Option<EpicAuths>,
}

impl PathMetadata {
/// Reverses the path metadata such that it applies to the reversed path.
pub fn reverse(&mut self) -> &mut Self {
self.interfaces[..].reverse();
if let Some(v) = self.latency.as_mut() {
v[..].reverse()
}
if let Some(v) = self.bandwidth_kbps.as_mut() {
v[..].reverse()
}
if let Some(v) = self.geo.as_mut() {
v[..].reverse()
}
if let Some(v) = self.link_type.as_mut() {
v[..].reverse()
}
if let Some(v) = self.internal_hops.as_mut() {
v[..].reverse()
}
if let Some(v) = self.notes.as_mut() {
v[..].reverse()
}
self.epic_auths = None;
self
}
}

macro_rules! some_if_length_matches {
($input_vec:expr, $expected_length:expr, $result:expr) => {
if $input_vec.len() == $expected_length {
Expand Down
114 changes: 106 additions & 8 deletions crates/scion-proto/src/path/standard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

use std::mem;

use bytes::{Buf, Bytes};
use bytes::{Buf, BufMut, Bytes, BytesMut};

use super::DataplanePathErrorKind;
use crate::{
packet::DecodeError,
wire_encoding::{self, WireDecode},
packet::{DecodeError, InadequateBufferSize},
wire_encoding::{self, WireDecode, WireEncode},
};

wire_encoding::bounded_uint! {
Expand Down Expand Up @@ -66,9 +66,10 @@ pub struct PathMetaHeader {
impl PathMetaHeader {
/// The length of a path meta header in bytes.
pub const LENGTH: usize = 4;

const INFO_FIELD_LENGTH: usize = 8;
const HOP_FIELD_LENGTH: usize = 12;
/// The length of an info field in bytes
pub const INFO_FIELD_LENGTH: usize = 8;
/// The length of a hop field in bytes
pub const HOP_FIELD_LENGTH: usize = 12;

/// The number of info fields.
pub const fn info_fields_count(&self) -> usize {
Expand All @@ -80,7 +81,7 @@ impl PathMetaHeader {
}
}

/// Return the index of the current info field;
/// Returns the index of the current info field.
pub fn info_field_index(&self) -> usize {
self.current_info_field.get().into()
}
Expand All @@ -92,11 +93,33 @@ impl PathMetaHeader {
+ self.segment_lengths[2].length()
}

/// Return the index of the current hop field;
/// Returns the index of the current hop field.
pub fn hop_field_index(&self) -> usize {
self.current_hop_field.get().into()
}

/// Returns the offset in bytes of the given info field
pub fn info_field_offset(info_field_index: usize) -> usize {
Self::LENGTH + Self::INFO_FIELD_LENGTH * info_field_index
}

/// Returns the offset in bytes of the given hop field
pub fn hop_field_offset(&self, hop_field_index: usize) -> usize {
Self::LENGTH
+ Self::INFO_FIELD_LENGTH * self.info_fields_count()
+ Self::HOP_FIELD_LENGTH * hop_field_index
}

/// Encodes the header as a `u32`
pub fn as_u32(&self) -> u32 {
(u32::from(self.current_info_field.get()) << 30)
| (u32::from(self.current_hop_field.get()) << 24)
| (u32::from(self.reserved.get()) << 18)
| (u32::from(self.segment_lengths[0].get()) << 12)
| (u32::from(self.segment_lengths[1].get()) << 6)
| (u32::from(self.segment_lengths[2].get()))
}

const fn encoded_path_length(&self) -> usize {
Self::LENGTH
+ self.info_fields_count() * Self::INFO_FIELD_LENGTH
Expand All @@ -120,6 +143,20 @@ impl PathMetaHeader {
}
}

impl WireEncode for PathMetaHeader {
type Error = InadequateBufferSize;

#[inline]
fn encoded_length(&self) -> usize {
Self::LENGTH
}

#[inline]
fn encode_to_unchecked<T: BufMut>(&self, buffer: &mut T) {
buffer.put_u32(self.as_u32());
}
}

impl<T: Buf> WireDecode<T> for PathMetaHeader {
type Error = DecodeError;

Expand Down Expand Up @@ -209,6 +246,67 @@ impl StandardPath {
pub fn raw(&self) -> Bytes {
self.encoded_path.clone()
}

/// Reverses both the raw path and the metadata in the [`Self::meta_header`].
///
/// Can panic if the meta header is inconsistent with the encoded path or the encoded path
/// itself is inconsistent (e.g., the `current_info_field` points to an empty segment).
pub fn reverse(&self) -> Self {
let meta_header = PathMetaHeader {
current_info_field: (self.meta_header.info_fields_count() as u8
- self.meta_header.current_info_field.get()
- 1)
.into(),
current_hop_field: (self.meta_header.hop_fields_count() as u8
- self.meta_header.current_hop_field.get()
- 1)
.into(),
reserved: PathMetaReserved::default(),
segment_lengths: match self.meta_header.segment_lengths {
[SegmentLength(0), ..] => [SegmentLength(0); 3],
[s1, SegmentLength(0), ..] => [s1, SegmentLength(0), SegmentLength(0)],
[s1, s2, SegmentLength(0)] => [s2, s1, SegmentLength(0)],
[s1, s2, s3] => [s3, s2, s1],
},
};

let mut encoded_path = BytesMut::with_capacity(self.encoded_path.len());
meta_header.encode_to_unchecked(&mut encoded_path);
self.write_reversed_info_fields_to(&mut encoded_path);
self.write_reversed_hop_fields_to(&mut encoded_path);

Self {
meta_header,
encoded_path: encoded_path.freeze(),
}
}

/// Writes the info fields to the provided buffer in reversed order.
///
/// This also flips the "construction direction flag" for all info fields.
fn write_reversed_info_fields_to(&self, buffer: &mut BytesMut) {
for info_field in (0..self.meta_header.info_fields_count()).rev() {
let offset = PathMetaHeader::info_field_offset(info_field);
let slice = &self
.encoded_path
.slice(offset..offset + PathMetaHeader::INFO_FIELD_LENGTH);

buffer.put_u8(slice[0] ^ 0b1); // Flip construction direction flag
buffer.put_slice(&slice[1..]);
}
}

/// Writes the hop fields to the provided buffer in reversed order.
fn write_reversed_hop_fields_to(&self, buffer: &mut BytesMut) {
for hop_field in (0..self.meta_header.hop_fields_count()).rev() {
let offset = self.meta_header().hop_field_offset(hop_field);
buffer.put_slice(
&self
.encoded_path
.slice(offset..offset + PathMetaHeader::HOP_FIELD_LENGTH),
)
}
}
}

impl WireDecode<Bytes> for StandardPath {
Expand Down
33 changes: 22 additions & 11 deletions crates/scion/tests/test_udp_socket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ type TestError = Result<(), Box<dyn std::error::Error>>;

static MESSAGE: Bytes = Bytes::from_static(b"Hello SCION!");

macro_rules! test_send_and_receive {
macro_rules! test_send_receive_reply {
($name:ident, $source:expr, $destination:expr) => {
#[tokio::test]
#[ignore = "requires daemon and dispatcher"]
Expand All @@ -25,36 +25,47 @@ macro_rules! test_send_and_receive {
let socket_destination = UdpSocket::bind(endpoints.destination).await?;

socket_source.connect(endpoints.destination);
socket_source.set_path(
daemon_client_source
.paths_to(endpoints.destination.isd_asn())
.await?
.next()
.unwrap(),
);
let path_forward = daemon_client_source
.paths_to(endpoints.destination.isd_asn())
.await?
.next()
.unwrap();
println!("Forward path: {:?}", path_forward.dataplane_path);
socket_source.set_path(path_forward);
socket_source.send(MESSAGE.clone()).await?;

let mut buffer = [0_u8; 100];
let (length, sender, _path) = tokio::time::timeout(
let (length, sender, mut path) = tokio::time::timeout(
std::time::Duration::from_secs(1),
socket_destination.recv_from(&mut buffer),
)
.await??;
assert_eq!(sender, endpoints.source);
assert_eq!(buffer[..length], MESSAGE[..]);

path.reverse()?;
println!("Reply path: {:?}", path.dataplane_path);
socket_destination
.send_to_with(MESSAGE.clone(), sender, &path)
.await?;
let _ = tokio::time::timeout(
std::time::Duration::from_secs(1),
socket_source.recv_from(&mut buffer),
)
.await??;

Ok(())
}
};
}

test_send_and_receive!(
test_send_receive_reply!(
send_and_receive_up_and_down_segment,
"[1-ff00:0:111,127.0.0.17]:12345",
"[1-ff00:0:112,fd00:f00d:cafe::7f00:a]:443"
);

test_send_and_receive!(
test_send_receive_reply!(
send_and_receive_same_as,
"[1-ff00:0:111,127.0.0.17]:12346",
"[1-ff00:0:111,127.0.0.17]:8080"
Expand Down

0 comments on commit 52ec651

Please sign in to comment.