#![doc = simple_mermaid::mermaid!("../../pocketscion/src/util/topologies/diagrams/two_path.mmd")]
mod common;
use std::time::Duration;
use pocketscion::util::topologies::{
IA132, IA212, IA222, UnderlayType, minimal::two_path_topology,
};
use scion_stack::{
path::policy::PathPolicy,
stack::{SocketConfig, UdpScionSocket},
};
use sciparse::{
address::ip_socket_addr::ScionSocketIpAddr, identifier::isd_asn::IsdAsn, path::ScionPath,
};
use tokio::time::timeout;
const MAX_DATAGRAM: usize = 2048;
struct AvoidAs {
avoid: IsdAsn,
}
impl PathPolicy for AvoidAs {
fn predicate(&self, path: &ScionPath) -> bool {
let Some(interfaces) = path.metadata().and_then(|m| m.interfaces.as_ref()) else {
return true;
};
!interfaces
.iter()
.any(|hop| hop.interface.isd_asn == self.avoid)
}
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
run().await
}
async fn run() -> anyhow::Result<()> {
scion_sdk_utils::rustls::select_ring_crypto_provider();
let ps = two_path_topology(UnderlayType::Snap).await;
let server_stack = common::build_stack(&ps, IA212).await?;
let server_socket = server_stack.bind(None).await?;
let server_addr = server_socket.local_addr();
let server = tokio::spawn(echo_server(server_socket));
let client_stack = common::build_stack(&ps, IA132).await?;
let config = SocketConfig::new().with_path_policy(AvoidAs { avoid: IA222 });
let client_socket = client_stack.bind_with_config(None, config).await?;
let reply = ping(
&client_socket,
server_addr,
b"hello via a policy-approved path",
)
.await?;
println!("client received echo: {}", String::from_utf8_lossy(&reply));
server.abort();
Ok(())
}
async fn ping(
socket: &UdpScionSocket,
destination: ScionSocketIpAddr,
payload: &[u8],
) -> anyhow::Result<Vec<u8>> {
let mut buffer = [0u8; MAX_DATAGRAM];
for attempt in 1..=5 {
socket.send_to(payload, destination).await?;
match timeout(Duration::from_millis(500), socket.recv_from(&mut buffer)).await {
Ok(result) => {
let (len, _from) = result?;
return Ok(buffer[..len].to_vec());
}
Err(_elapsed) => tracing::debug!(attempt, "no echo yet, resending"),
}
}
anyhow::bail!("no echo received from {destination} after 5 attempts")
}
async fn echo_server(socket: UdpScionSocket) -> anyhow::Result<()> {
let mut buffer = [0u8; MAX_DATAGRAM];
loop {
let (len, from) = socket.recv_from(&mut buffer).await?;
socket.send_to(&buffer[..len], from).await?;
}
}
#[cfg(test)]
mod tests {
use test_log::test;
#[test(tokio::test)]
#[ntest::timeout(30_000)]
async fn udp_path_policy_reaches_server() {
super::run()
.await
.expect("udp_path_policy example should succeed");
}
}