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 path type with metadata and basic daemon requests #24

Merged
merged 10 commits into from
Nov 21, 2023
4 changes: 2 additions & 2 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,10 @@ jobs:
with:
save-if: "false"
- run: sudo apt-get install protobuf-compiler
- run: cargo install cargo-tarpaulin@0.26.1
- run: cargo install cargo-tarpaulin@0.27.1

- name: Run tests and record coverage
run: cargo tarpaulin --workspace --skip-clean --all-targets --doc --out html --out xml
run: cargo tarpaulin --workspace --skip-clean --all-targets --doc --out html --out xml --exclude-files "crates/scion-grpc/*"

- name: Upload coverage report
uses: actions/upload-artifact@v3
Expand Down
7 changes: 7 additions & 0 deletions crates/scion/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,12 @@ publish = false

[dependencies]
bytes = "1.5.0"
chrono = { version = "0.4.31", default-features = false }
scion-grpc = { version = "0.1.0", path = "../scion-grpc" }
serde = { version = "1.0.188", features = ["derive"] }
thiserror = "1.0.48"
tonic = "0.10.2"
tracing = "0.1.40"

[dev-dependencies]
prost-types = "0.12.2"
7 changes: 5 additions & 2 deletions crates/scion/src/daemon.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
mod types;
pub use types::{AsInfo, PathRequestFlags};
mod messages;
pub use messages::{AsInfo, PathRequest};

mod client;
pub use client::{DaemonClient, DaemonClientError};
112 changes: 112 additions & 0 deletions crates/scion/src/daemon/client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
use scion_grpc::daemon::{v1 as daemon_grpc, v1::daemon_service_client::DaemonServiceClient};
use thiserror::Error;
use tonic::transport::Channel;
use tracing::warn;

use super::{messages::PathRequest, AsInfo};
use crate::{address::IsdAsn, packet::ByEndpoint, path::Path};

#[derive(Debug, Error)]
pub enum DaemonClientError {
#[error("A communication error occurred: {0}")]
ConnectionError(#[from] tonic::transport::Error),
#[error("A gRPC error occurred: {0}")]
GrpcError(#[from] tonic::Status),
#[error("Response contained invalid data")]
InvalidData,
}

/// A service to communicate with the local SCION daemon
#[derive(Clone, Debug)]
pub struct DaemonClient {
connection: Channel,
local_isd_asn: IsdAsn,
}

impl DaemonClient {
/// Create a new client to communicate with the SCION daemon located at `address`
///
/// This attempts to connect to the daemon directly and fetch the local ISD-ASN.
///
/// Errors:
///
/// This returns an error, if any error occurs during the connection setup or during the request
/// for the AS info.
pub async fn connect(address: &str) -> Result<Self, DaemonClientError> {
let mut client = Self {
connection: tonic::transport::Endpoint::new(address.to_string())?
.connect()
.await?,
local_isd_asn: IsdAsn::WILDCARD,
};
client.local_isd_asn = client.as_info(IsdAsn::WILDCARD).await?.isd_asn;

Ok(client)
}

/// Request information about an AS; [`IsdAsn::WILDCARD`] can be used to obtain information
/// about the local AS.
pub async fn as_info(&self, isd_asn: IsdAsn) -> Result<AsInfo, DaemonClientError> {
self.client()
.r#as(daemon_grpc::AsRequest::from(isd_asn))
.await?
.into_inner()
.try_into()
.map_err(|_| DaemonClientError::InvalidData)
}

/// Request a set of end-to-end paths between the source and destination AS
pub async fn paths(&self, request: &PathRequest) -> Result<Paths, DaemonClientError> {
let src_isd_asn = if request.source.is_wildcard() {
self.local_isd_asn
} else {
request.source
};
let isd_asn = ByEndpoint {
source: src_isd_asn,
destination: request.destination,
};
Ok(Paths {
isd_asn,
grpc_paths: self
.client()
.paths(daemon_grpc::PathsRequest::from(request))
.await?
.into_inner()
.paths
.into_iter(),
})
}

#[inline]
pub async fn paths_to(
&self,
destination: IsdAsn,
) -> Result<impl Iterator<Item = Path>, DaemonClientError> {
self.paths(&PathRequest::new(destination)).await
}

fn client(&self) -> DaemonServiceClient<Channel> {
DaemonServiceClient::new(self.connection.clone())
}
}

/// Iterator for SCION [Path]s obtained from the SCION Daemon via gRPC
pub struct Paths {
isd_asn: ByEndpoint<IsdAsn>,
grpc_paths: std::vec::IntoIter<daemon_grpc::Path>,
}

impl Iterator for Paths {
type Item = Path;

fn next(&mut self) -> Option<Self::Item> {
for grpc_path in self.grpc_paths.by_ref() {
match Path::try_from_grpc_with_endpoints(grpc_path, self.isd_asn) {
Ok(path) => return Some(path),
Err(e) => warn!(?e, "a parse error occurred for a path"),
}
}
None
}
}
170 changes: 170 additions & 0 deletions crates/scion/src/daemon/messages.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
use std::num::TryFromIntError;

use scion_grpc::daemon::v1::{self as daemon_grpc};

use crate::address::IsdAsn;

impl From<IsdAsn> for daemon_grpc::AsRequest {
fn from(value: IsdAsn) -> Self {
Self {
isd_as: value.as_u64(),
}
}
}

/// Information about an AS
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct AsInfo {
/// The AS's ISD-ASN
pub isd_asn: IsdAsn,
/// Is the AS a core AS?
pub core: bool,
/// The maximum transmission unit (MTU) in the AS
pub mtu: u16,
}

impl TryFrom<daemon_grpc::AsResponse> for AsInfo {
type Error = TryFromIntError;
fn try_from(value: daemon_grpc::AsResponse) -> Result<Self, Self::Error> {
Ok(Self {
isd_asn: value.isd_as.into(),
core: value.core,
mtu: value.mtu.try_into()?,
})
}
}

/// Path requests specifying source and destination ISD-ASN with some flags
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct PathRequest {
pub source: IsdAsn,
pub destination: IsdAsn,
pub flags: PathRequestFlags,
}

/// Flags for path requests
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct PathRequestFlags {
pub refresh: bool,
pub hidden: bool,
}

impl From<&PathRequest> for daemon_grpc::PathsRequest {
fn from(value: &PathRequest) -> Self {
Self {
source_isd_as: value.source.as_u64(),
destination_isd_as: value.destination.as_u64(),
refresh: value.flags.refresh,
hidden: value.flags.hidden,
}
}
}

impl PathRequest {
pub fn new(dst_isd_asn: IsdAsn) -> Self {
Self {
source: IsdAsn::WILDCARD,
destination: dst_isd_asn,
flags: PathRequestFlags::default(),
}
}

pub fn with_src_isd_asn(mut self, src_isd_asn: IsdAsn) -> Self {
self.source = src_isd_asn;
self
}

pub fn with_refresh(mut self) -> Self {
self.flags.refresh = true;
self
}

pub fn with_hidden(mut self) -> Self {
self.flags.hidden = true;
self
}
}

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

mod as_info {
use super::*;

#[test]
fn request_conversion() {
assert_eq!(
daemon_grpc::AsRequest::from(IsdAsn::from(42)),
daemon_grpc::AsRequest { isd_as: 42 }
)
}

#[test]
fn response_conversion() {
assert_eq!(
AsInfo::try_from(daemon_grpc::AsResponse {
isd_as: 42,
core: true,
mtu: 1500
}),
Ok(AsInfo {
isd_asn: IsdAsn::from(42),
core: true,
mtu: 1500
})
)
}
}

mod path {
use super::*;

#[test]
fn grpc_conversion() {
assert_eq!(
daemon_grpc::PathsRequest::from(&PathRequest::new(IsdAsn::from(1))),
daemon_grpc::PathsRequest {
source_isd_as: 0,
destination_isd_as: 1,
refresh: false,
hidden: false,
}
)
}

#[test]
fn full_construction() {
let source = IsdAsn::from(42);
let destination = IsdAsn::from(314);
let request = PathRequest::new(destination);
assert_eq!(
request,
PathRequest {
source: IsdAsn::WILDCARD,
destination,
flags: PathRequestFlags::default()
}
);
assert_eq!(
request.with_src_isd_asn(source),
PathRequest {
source,
destination,
flags: PathRequestFlags::default()
}
);
assert_eq!(
request.with_hidden().with_refresh(),
PathRequest {
source: IsdAsn::WILDCARD,
destination,
flags: PathRequestFlags {
refresh: true,
hidden: true
}
}
);
}
}
}
15 changes: 0 additions & 15 deletions crates/scion/src/daemon/types.rs

This file was deleted.

12 changes: 6 additions & 6 deletions crates/scion/src/packet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
use bytes::{Buf, Bytes};

use crate::{
path::PathErrorKind,
path::DataplanePathErrorKind,
wire_encoding::{WireDecode, WireDecodeWithContext},
};

Expand All @@ -24,8 +24,8 @@ pub use path_header::PathHeader;
/// Instances of an object associated with both a source and destination endpoint.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct ByEndpoint<T> {
destination: T,
source: T,
pub destination: T,
pub source: T,
}

/// A SCION network packet.
Expand Down Expand Up @@ -92,11 +92,11 @@ pub enum DecodeError {
#[error("attempted to decode the empty path type")]
EmptyPath,
#[error("invalid path header: {0}")]
InvalidPath(PathErrorKind),
InvalidPath(DataplanePathErrorKind),
}

impl From<PathErrorKind> for DecodeError {
fn from(value: PathErrorKind) -> Self {
impl From<DataplanePathErrorKind> for DecodeError {
fn from(value: DataplanePathErrorKind) -> Self {
Self::InvalidPath(value)
}
}
Loading