Skip to content

Commit

Permalink
feat: add registration message for registering ports
Browse files Browse the repository at this point in the history
  • Loading branch information
jpcsmith committed Oct 4, 2023
1 parent 8f2a7be commit 4fdc98d
Show file tree
Hide file tree
Showing 6 changed files with 276 additions and 4 deletions.
6 changes: 6 additions & 0 deletions crates/scion/src/address/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,12 @@ impl FromStr for ServiceAddress {
}
}

impl From<ServiceAddress> for u16 {
fn from(value: ServiceAddress) -> Self {
value.0
}
}

impl Display for ServiceAddress {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self.anycast() {
Expand Down
3 changes: 3 additions & 0 deletions crates/scion/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
pub mod address;
pub mod daemon;
pub mod reliable;

#[cfg(test)]
pub(crate) mod test_utils;
1 change: 1 addition & 0 deletions crates/scion/src/reliable.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mod common_header;
pub use common_header::DecodeError;

mod registration;
mod wire_utils;

const ADDRESS_TYPE_OCTETS: usize = 1;
5 changes: 1 addition & 4 deletions crates/scion/src/reliable/common_header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,10 +150,7 @@ impl CommonHeader {

/// The number of bytes in the encoded common header.
pub fn encoded_length(&self) -> usize {
Self::COOKIE_LENGTH
+ ADDRESS_TYPE_OCTETS
+ Self::PAYLOAD_SIZE_LENGTH
+ encoded_address_and_port_length(self.destination.host_address_type())
Self::MIN_LENGTH + encoded_address_and_port_length(self.destination.host_address_type())
}

/// Serialize a common header to the provided buffer.
Expand Down
258 changes: 258 additions & 0 deletions crates/scion/src/reliable/registration.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
//! SCION port registration request and response.
//!
//! This module contains the request and response messages for registering ports
//! with the SCION dispatcher, on which the application would like to receive
//! packets.
//!
//! The encoded format of the request is
//!
//! ```plain
//! 13-bytes : Common header with address type NONE
//! 1-byte : Command (bit mask with 0x04=Bind address, 0x02=SCMP enable, 0x01 always set)
//! 1-byte : L4 Proto (IANA number)
//! 8-bytes : ISD-AS
//! 2-bytes : Public L4 port
//! 1-byte : Public Address type
//! var-byte : Public Address
//! 2-bytes : L4 bind port ┐
//! 1-byte : Address type ├ (optional bind address)
//! var-byte : Bind Address ┘
//! 2-bytes : SVC (optional SVC type)
//! ```
//!
//! whereas the response is the 2-byte port assigned by the dispatcher.
//!
use std::net::{IpAddr, SocketAddr};

use bytes::{Buf, BufMut};

use super::wire_utils::LAYER4_PORT_OCTETS;
use crate::{
address::{HostAddress, IsdAsn, ServiceAddress},
reliable::{
common_header::CommonHeader,
wire_utils::{encoded_address_and_port_length, encoded_address_length},
ADDRESS_TYPE_OCTETS,
},
};

/// A SCION port registration request to the dispatcher.
pub(super) struct RegistrationRequest {
/// The SCION AS in which the application is registering to listen for packets.
pub isd_asn: IsdAsn,
/// The address and port to which the remote SCION endpoint will be sending packets.
pub public_address: SocketAddr,
/// A separate "bind address", only used for logging by the dispatcher.
pub bind_address: Option<SocketAddr>,
/// The service to associate with
pub associated_service: Option<ServiceAddress>,
}

impl RegistrationRequest {
/// Return a new registration request for the specified IsdAsn and public address.
pub fn new(isd_asn: IsdAsn, public_address: SocketAddr) -> Self {
Self {
isd_asn,
public_address,
bind_address: None,
associated_service: None,
}
}

/// Add the provided bind address to the request.
#[cfg(test)]
pub fn with_bind_address(mut self, address: SocketAddr) -> Self {
self.bind_address = Some(address);
self
}

/// Add the provided associated service address to the request.
pub fn with_associated_service(mut self, address: ServiceAddress) -> Self {
self.associated_service = Some(address);
self
}

#[allow(dead_code)]
pub fn encoded_length(&self) -> usize {
CommonHeader::MIN_LENGTH + self.encoded_request_length()
}

/// Encode a registration request to the provided buffer.
///
/// # Panics
///
/// Panics if there is not enough space in the buffer to encode the request.
#[allow(dead_code)]
pub fn encode_to(&self, buffer: &mut impl BufMut) {
self.encode_common_header(buffer);
self.encode_request(buffer);
}

fn encode_request(&self, buffer: &mut impl BufMut) {
let initial_remaining = buffer.remaining_mut();

const UDP_PROTOCOL_NUMBER: u8 = 17;

self.encode_command_flag(buffer);

buffer.put_u8(UDP_PROTOCOL_NUMBER);
buffer.put_u64(self.isd_asn.as_u64());

encode_address(buffer, &self.public_address);

if let Some(bind_address) = self.bind_address.as_ref() {
encode_address(buffer, bind_address)
}
if let Some(service_address) = self.associated_service.as_ref() {
buffer.put_u16(u16::from(*service_address))
}

let written = initial_remaining - buffer.remaining_mut();
assert_eq!(written, self.encoded_request_length());
}

#[allow(dead_code)]
#[inline]
fn encode_common_header(&self, buffer: &mut impl BufMut) {
CommonHeader {
destination: None,
payload_length: u32::try_from(self.encoded_request_length())
.expect("requests are short"),
}
.encode_to(buffer);
}

#[inline]
fn encoded_request_length(&self) -> usize {
const BASE_LENGTH: usize = 13;

BASE_LENGTH
+ encoded_address_length(self.public_address.host_address_type())
+ if self.bind_address.is_some() {
ADDRESS_TYPE_OCTETS
+ encoded_address_and_port_length(self.bind_address.host_address_type())
} else {
0
}
+ encoded_address_and_port_length(self.associated_service.host_address_type())
}

#[inline]
fn encode_command_flag(&self, buffer: &mut impl BufMut) {
const FLAG_BASE: u8 = 0b001;
const FLAG_SCMP: u8 = 0b010;
const FLAG_BIND: u8 = 0b100;

buffer.put_u8(FLAG_BASE | FLAG_SCMP | self.bind_address.map_or(0u8, |_| FLAG_BIND));
}
}

/// The response to a registration request.
///
/// Only successful registrations are responded to by the dispatcher, thus
/// this always contains the assigned port number.
pub(super) struct RegistrationResponse {
/// The port assigned by the dispatcher.
#[allow(dead_code)]
pub assigned_port: u16,
}

impl RegistrationResponse {
/// The length of the encoded registration response.
#[allow(dead_code)]
pub const ENCODED_LENGTH: usize = LAYER4_PORT_OCTETS;

/// Decode a registration response from the provided buffer.
///
/// Returns None if the buffer contains less than 2 bytes.
#[allow(dead_code)]
pub fn decode(buffer: &mut impl Buf) -> Option<Self> {
if buffer.remaining() >= Self::ENCODED_LENGTH {
Some(Self {
assigned_port: buffer.get_u16(),
})
} else {
None
}
}
}

fn encode_address(buffer: &mut impl BufMut, address: &SocketAddr) {
buffer.put_u16(address.port());
buffer.put_u8(address.host_address_type().into());

match address.ip() {
IpAddr::V4(ipv4) => buffer.put(ipv4.octets().as_slice()),
IpAddr::V6(ipv6) => buffer.put(ipv6.octets().as_slice()),
}
}

#[cfg(test)]
mod tests {
use super::*;

mod encode {
use super::*;
use crate::test_utils::parse;

const BUFFER_LENGTH: usize = 50;

macro_rules! test_successful {
($name:ident, $request:expr, $expected:expr) => {
#[test]
fn $name() {
let mut backing_array = [0u8; BUFFER_LENGTH];
let mut buffer = backing_array.as_mut_slice();

$request.encode_request(&mut buffer);

let bytes_written = BUFFER_LENGTH - buffer.remaining_mut();
assert_eq!(backing_array[..bytes_written], $expected);
}
};
}

test_successful!(
public_ipv4_only,
RegistrationRequest::new(parse!("1-ff00:0:1"), parse!("10.2.3.4:80")),
[0x03, 17, 0, 1, 0xff, 0, 0, 0, 0, 0x01, 0, 80, 1, 10, 2, 3, 4]
);

test_successful!(
public_ipv6_only,
RegistrationRequest::new(parse!("1-ff00:0:1"), parse!("[2001:db8::1]:80")),
[
0x03, 17, 0, 1, 0xff, 0, 0, 0, 0, 0x01, 0, 80, 2, 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1
]
);

test_successful!(
public_with_bind,
RegistrationRequest::new(parse!("1-ff00:0:1"), parse!("10.2.3.4:80"))
.with_bind_address(parse!("10.5.6.7:81")),
[
0x07, 17, 0, 1, 0xff, 0, 0, 0, 0, 0x01, 0, 80, 1, 10, 2, 3, 4, 0, 81, 1, 10, 5, 6,
7
]
);

test_successful!(
public_ipv4_with_service,
RegistrationRequest::new(parse!("1-ff00:0:1"), parse!("10.2.3.4:80"))
.with_associated_service(ServiceAddress::CONTROL),
[0x03, 17, 0, 1, 0xff, 0, 0, 0, 0, 0x01, 0, 80, 1, 10, 2, 3, 4, 0x00, 0x02]
);

test_successful!(
with_bind_and_service,
RegistrationRequest::new(parse!("1-ff00:0:1"), parse!("10.2.3.4:80"))
.with_bind_address(parse!("10.5.6.7:81"))
.with_associated_service(ServiceAddress::CONTROL),
[
0x07, 17, 0, 1, 0xff, 0, 0, 0, 0, 0x01, 0, 80, 1, 10, 2, 3, 4, 0, 81, 1, 10, 5, 6,
7, 0, 2
]
);
}
}
7 changes: 7 additions & 0 deletions crates/scion/src/test_utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
macro_rules! parse {
($string:literal) => {
$string.parse().unwrap()
};
}

pub(crate) use parse;

0 comments on commit 4fdc98d

Please sign in to comment.