Skip to main content
SCION SDKv0.6.0

Getting started

This guide takes you from nothing to a running SCION program in a few minutes. You do not need access to a real SCION network, and you do not need to know anything about SCION yet. Everything runs locally against PocketSCION, a SCION network simulator that ships with the SDK.

By the end you will have compiled and run a UDP echo client/server that talks over a simulated SCION network you started yourself.

What SCION gives you as an application developer

SCION is an inter-domain networking architecture — think of it as an alternative to today's BGP-routed Internet — with one property that matters most to application developers: the application, not the network, chooses the path its packets take.

On the SCION network you can:

  • See every path to a destination and their properties (which ISDs and ASes they cross, MTU, latency hints) instead of being handed one opaque route.
  • Pick a path per packet — steer traffic away from a provider, prefer a low-latency route, or spread load across several paths — from application code.
  • Fail over instantly when a path breaks, because you already hold the alternatives.
  • Trust the source, because SCION paths are cryptographically authenticated.

The SDK's scion-stack crate exposes this through an API deliberately shaped like the sockets you already know: bind, send_to, recv_from. Path awareness is opt-in — the simplest programs ignore it and let the stack choose, and you reach for explicit path selection only when you want it (see Going further: let your app pick the path).

Prerequisites

  • Rust — the SDK pins its toolchain in rust-toolchain.toml, so if you have rustup installed the right version is fetched automatically.
  • git, to clone the repository.
  • A supported platform: Linux, macOS, or Windows.

No SCION installation, no root, no network access beyond fetching crates.

Run your first SCION program

The fastest path to "it works" is to run the udp_echo example that ships with the SDK. Clone the repository and run it with a single command:

git clone https://github.com/Anapaya/scion-sdk.git
cd scion-sdk
cargo run -p scion-stack --example udp_echo

You should see:

echo server listening on <address in 2-ff00:0:212>
client received echo: Hello, SCION!

That's a full SCION round-trip: a client in one autonomous system (AS) sent a datagram to a server in a different AS, across a SCION network that the example started itself, and read back the echo.

What just happened

The example builds this two-AS network in PocketSCION — a client AS and a server AS joined by one link:

Here is the whole program. We'll walk through it step by step below.

scion-stack/examples/udp_echo.rs
// Copyright 2026 Anapaya Systems
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! "Hello, SCION" over UDP.
//!
//! A client in one AS sends a datagram to an echo server in another AS and prints
//! the reply. Everything runs against a [PocketSCION] network that this example
//! starts itself.
//!
//! The network is two ASes joined by a single link:
//!
//! ```text
//! 1-ff00:0:132 #1 ───────── #3 2-ff00:0:212
//! (client) (server)
//! ```
//!
//! Run it with:
//!
//! ```text
//! cargo run -p scion-stack --example udp_echo
//! ```
//!
//! [PocketSCION]: pocketscion

mod common;

use std::time::Duration;

use pocketscion::util::topologies::{IA132, IA212, UnderlayType, minimal::minimal_topology};
use scion_stack::stack::UdpScionSocket;
use sciparse::address::ip_socket_addr::ScionSocketIpAddr;
use tokio::time::timeout;

/// Largest datagram we bother to buffer in this example.
const MAX_DATAGRAM: usize = 2048;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
run().await
}

/// Starts a two-AS SCION network, runs an echo server, and pings it once.
async fn run() -> anyhow::Result<()> {
// PocketSCION uses rustls for its control plane; pick a crypto backend.
scion_sdk_utils::rustls::select_ring_crypto_provider();

// Start a minimal SCION network: two ASes joined by a single link. The returned
// handle owns the whole simulation.
let ps = minimal_topology(UnderlayType::Snap).await;

// Bring up the echo server in AS 2-ff00:0:212 and let it run in the background.
let server_stack = common::build_stack(&ps, IA212).await?;
let server_socket = server_stack.bind(None).await?;
let server_addr = server_socket.local_addr();
println!("echo server listening on {server_addr}");
let server = tokio::spawn(echo_server(server_socket));

// Bring up the client in AS 1-ff00:0:132 and send one datagram.
let client_stack = common::build_stack(&ps, IA132).await?;
let client_socket = client_stack.bind(None).await?;

let reply = ping(&client_socket, server_addr, b"Hello, SCION!").await?;
println!("client received echo: {}", String::from_utf8_lossy(&reply));

server.abort();
Ok(())
}

/// Echoes every datagram straight back to whoever sent it, forever.
///
/// [`recv_from`](UdpScionSocket::recv_from) yields the sender's SCION address, and
/// the socket automatically remembers a return path to it, so [`send_to`] can
/// reply without the application ever touching path selection.
///
/// [`send_to`]: UdpScionSocket::send_to
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?;
}
}

/// Sends `payload` to `destination` and waits for the echoed reply.
///
/// UDP datagrams can be dropped, so we resend a handful of times before giving up.
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")
}

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

/// End-to-end smoke test: the example must complete a full echo round-trip.
#[test(tokio::test)]
#[ntest::timeout(30_000)]
async fn udp_echo_roundtrip() {
super::run().await.expect("udp_echo example should succeed");
}
}

1. Start a SCION network

minimal_topology brings up the two-AS network shown above and returns a handle that owns the whole simulation. This is the only PocketSCION-specific line — against real SCION you would skip straight to building the stack.

udp_echo.rs
// Start a minimal SCION network: two ASes joined by a single link. The returned
// handle owns the whole simulation.
let ps = minimal_topology(UnderlayType::Snap).await;

2. Attach a SCION stack to an AS

A ScionStack is the SCION equivalent of your OS networking stack: the object you open sockets on. You build one per AS you want to run code in. The examples wrap this in a build_stack helper:

examples/common/mod.rs
pub async fn build_stack(ps: &PsSetup, isd_as: IsdAsn) -> anyhow::Result<ScionStack> {
let endhost_api = ps
.endhost_api(isd_as)
.with_context(|| format!("PocketSCION has no endhost API for {isd_as}"))?;

ScionStackBuilder::new()
.with_endhost_api(endhost_api)
.with_auth_token(dev_auth_token())
.build()
.await
.with_context(|| format!("building SCION stack for {isd_as}"))
}

3. Bind a socket and serve

Bind a UDP socket on the server's stack and hand it to a task that echoes every datagram back to its sender. recv_from yields the sender's SCION address, and the socket selects a return path automatically, so send_to can reply without the application ever touching SCION path selection:

udp_echo.rs
// Bring up the echo server in AS 2-ff00:0:212 and let it run in the background.
let server_stack = common::build_stack(&ps, IA212).await?;
let server_socket = server_stack.bind(None).await?;
let server_addr = server_socket.local_addr();
println!("echo server listening on {server_addr}");
let server = tokio::spawn(echo_server(server_socket));
udp_echo.rs
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?;
}
}

4. Send from the client

Bind a socket on the client's stack and send one datagram to the server's address. send_to picks a SCION path for you; because UDP datagrams can be dropped, the helper resends a few times before giving up:

udp_echo.rs
// Bring up the client in AS 1-ff00:0:132 and send one datagram.
let client_stack = common::build_stack(&ps, IA132).await?;
let client_socket = client_stack.bind(None).await?;

let reply = ping(&client_socket, server_addr, b"Hello, SCION!").await?;
println!("client received echo: {}", String::from_utf8_lossy(&reply));
udp_echo.rs
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")
}

That is the whole surface you need for basic SCION networking: build a stack, bind a socket, send_to / recv_from. Path awareness — SCION's defining feature — is one method away when you want it.

Going further: let your app pick the path

Above, send_to chose a path for you. SCION's defining feature is that your application can choose a path from all available ones. Ask the stack's path fetcher for every path to the destination:

udp_paths.rs
// Ask the SDK for *every* path to the server's AS and inspect them ourselves,
// rather than letting `send_to` silently pick one for us.
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"
);

// Order shortest-first so the deliberate choice below is reproducible.
paths.sort_by_key(hop_count);

Each ScionPath carries metadata — the interfaces it traverses, MTU, latency hints — which is exactly what you'd base a routing decision on. Once you hold a path, send a datagram on the path with send_to_via instead of send_to:

udp_paths.rs
// Send over each path in turn, selecting it explicitly with `send_to_via`. The
// payload carries the path index so we can confirm the echo we accept is the
// reply to *this* send, not a delayed echo from an earlier 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");
}

You can also express a preference declaratively when building the stack, via ScionStackBuilder::with_path_policy and with_path_scoring, in which case send_to applies it automatically. The udp_paths example puts this all together against a topology with two paths to the server — run it the same way:

cargo run -p scion-stack --example udp_paths

Add the SDK to your own project

When you're ready to write your own program, add the stack to your crate:

cargo add scion-stack

To exercise it against a simulated network in your tests and examples, add PocketSCION as a dev-dependency:

cargo add --dev pocketscion

Then follow the same three steps as above: build a ScionStack, bind a socket, and send_to / recv_from.

Where to go next

  • Browse the examples — every example under scion-stack/examples/ is runnable with cargo run -p scion-stack --example <name> and is compiled and tested in CI.
  • API reference — the full API for the stack is on docs.rs/scion-stack.
  • PocketSCION — see pocketscion/README.md for the other topologies and underlays you can simulate.