mod common;
use std::time::Duration;
use pocketscion::util::topologies::{IA132, IA212, UnderlayType, minimal::two_path_topology};
use scion_stack::{path::fetcher::traits::PathFetcher, stack::UdpScionSocket};
use sciparse::{address::ip_socket_addr::ScionSocketIpAddr, path::ScionPath};
use tokio::time::timeout;
const MAX_DATAGRAM: usize = 2048;
#[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 client_socket = client_stack.bind(None).await?;
let mut paths = client_stack
.create_path_fetcher()
.fetch_paths(IA132, IA212)
.await?;
anyhow::ensure!(
!paths.is_empty(),
"expected at least one path to the server"
);
paths.sort_by_key(hop_count);
println!(
"found {} path(s) to {}:",
paths.len(),
server_addr.isd_asn()
);
for (i, path) in paths.iter().enumerate() {
println!(" [{i}] {path}");
}
for (i, path) in paths.iter().enumerate() {
let payload = format!("hello via path {i}");
ping_via(&client_socket, server_addr, path, payload.as_bytes()).await?;
println!(" [{i}] echo confirmed over this path");
}
let chosen = paths.last().expect("paths is non-empty");
println!("deliberately selected: {chosen}");
server.abort();
Ok(())
}
fn hop_count(path: &ScionPath) -> usize {
path.metadata()
.and_then(|m| m.interfaces.as_ref())
.map_or(0, Vec::len)
}
async fn ping_via(
socket: &UdpScionSocket,
destination: ScionSocketIpAddr,
path: &ScionPath,
payload: &[u8],
) -> anyhow::Result<()> {
let mut buffer = [0u8; MAX_DATAGRAM];
for _attempt in 1..=5 {
socket.send_to_via(payload, destination, path).await?;
while let Ok(result) =
timeout(Duration::from_millis(500), socket.recv_from(&mut buffer)).await
{
let (len, _from) = result?;
if &buffer[..len] == payload {
return Ok(());
}
tracing::debug!("ignoring echo that does not match our payload");
}
}
anyhow::bail!("no matching 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_paths_send_over_each_path() {
super::run()
.await
.expect("udp_paths example should succeed");
}
}