SERENADE

February 25, 2026 · View on GitHub

Satellite Emulation in Real-time for Advanced Network Development and Experimentation

SERENADE is a real-time, packet-level emulator for LEO satellite mega-constellations. It intercepts and forwards real IP traffic through an emulated non-terrestrial network (NTN), faithfully reproducing the propagation delays, rate-limited links, inter-satellite link (ISL) routing, and dynamic topology changes inherent to LEO constellations — all from a single commodity machine using Docker.

Terminology note: The paper refers to ground user nodes as User Terminals (UTs). In the codebase these are called Cells. Both terms refer to the same concept — a ground-level node representing one or more users at a given location that communicates with the constellation via a service satellite.


Key Features

  • Real traffic, real protocols. Client and server containers generate genuine IP packets (e.g., iperf3, ping, VoIP). SERENADE captures these at line rate using XDP/eBPF, routes them hop-by-hop through the emulated constellation, applies per-hop propagation delay and rate limiting, and re-injects them at the destination — all transparently to the applications.

  • Scalable background traffic. Every cell/UT in the scenario — not just those attached to external client containers — continuously generates background traffic at a configurable per-cell data rate. This is fundamental to producing realistic network conditions: thousands of cells competing for satellite capacity create the congestion, queuing, and packet drops that real NTN applications would experience. Background traffic rates can be adjusted at runtime, per-cell or globally.

  • Full mega-constellation emulation. Emulates Walker-Delta constellations with thousands of satellites (the default config models a 1,584-satellite constellation: 72 planes × 22 satellites, 500 km altitude, 53° inclination). Each satellite maintains four inter-satellite links (ISLs) and service/feeder links to ground.

  • Dynamic topology. Satellite positions are propagated in real-time using SGP4 (via Skyfield), and ground-to-satellite visibility, link delays, and handovers are recomputed periodically. Constellation time can be sped up to observe long-term dynamics in shorter wall-clock time.

  • Pluggable assignment engine. Cell-to-satellite and satellite-to-gateway assignments can be provided by any external algorithm — ship precomputed assignments as a Parquet file, or plug in your own service at runtime.

  • Runtime control API. A REST API lets you modify per-cell data rates, relocate user equipment on the fly, and trigger controlled test-mode measurements — all while the emulation is running.

  • Lightweight & containerized. The entire system runs as a set of Docker containers orchestrated by Docker Compose. No specialized hardware required.


Architecture

SERENADE is composed of six containerized components that communicate over isolated Docker networks:

SERENADE Architecture

ComponentLanguageRole
SERENADE CoreGoHeart of the emulator. Captures real packets via XDP/eBPF, routes them through emulated satellites, cells (able to generate their own, background traffic), and gateways, applies propagation delays and rate limiting, and delivers them to the destination.
Constellation EnginePythonMaintains satellite orbital state using SGP4 propagation (Skyfield). Serves satellite ITRS positions over HTTP. Supports configurable time speedup.
Assignment EnginePythonProvides cell 🡒 service-satellite 🡒 feeder-satellite 🡒 gateway assignments. Can serve precomputed assignments from a Parquet file or be replaced with a custom algorithm.
External ControlPythonREST API client for runtime interaction: modify data rates, move UE locations, trigger test-mode measurements.
Client / ServerShell/iperf3Real traffic endpoints. Traffic is routed through the emulated constellation transparently. Clients/server can be configured to run any arbitrary application.
Faux-DNSShellLightweight DNS bootstrapping so containers can discover each other before the emulation starts.

How It Works

  1. Packet capture. Client containers send real IP packets toward the server. The SERENADE Core container sits on the forwarding path and captures packets at the NIC level using an XDP/eBPF program — bypassing the kernel network stack for low overhead.

  2. Network emulation. Each captured packet is wrapped and injected into the emulated constellation as a Packet struct. It traverses cells, satellites (via ISLs), and gateways — each modeled as a concurrent Go goroutine with rate-limited ingress/egress channels.

  3. Background traffic. Simultaneously, every cell/UT in the scenario generates its own stream of dummy packets at a configurable data rate. These packets traverse the same satellites, ISLs, and gateways as real traffic — competing for link capacity, consuming buffer space, and inducing realistic congestion. This ensures that the real client traffic experiences network conditions representative of a loaded mega-constellation, not an empty one.

  4. Propagation delay. At every hop, the emulator computes the real propagation delay based on current node positions (satellite ITRS coordinates updated periodically from the Constellation Engine) and holds the packet in a wait zone until the delay elapses.

  5. Rate limiting. Uplink, downlink, and ISL data rates are enforced via token-bucket rate limiters. Elevation-dependent channel effects are modeled via configurable angle-based token scaling.

  6. Packet delivery. Once the packet exits the constellation (at a gateway for forward-link traffic, or at a cell for return-link traffic), SERENADE reconstructs the Ethernet frame and re-injects it onto the appropriate Docker network interface using XDP, reaching the destination container with realistic end-to-end delay.


Getting Started

Prerequisites

  • Docker and Docker Compose (v2+)
  • Linux host with kernel ≥ 5.10 (XDP/eBPF support)
  • Sufficient CPU and memory for the desired scenario to test

Quick Start

  1. Clone the repository:

    git clone https://github.com/serenade-project/SERENADE.git
    cd serenade
    
  2. Review the configuration in config.yaml. Key parameters:

    • num_planes, sats_per_plane, altitude, inclination — constellation geometry
    • rate_isl, rate_uldl — link data rates in Mbps
    • constellation_speedup — how fast constellation time evolves relative to wall-clock time
    • constellation_update_interval — how often satellite positions and links are recalculated (ms)
  3. Launch:

    sudo docker compose up --build
    

    This starts all six containers. The emulation begins automatically once the constellation engine, assignment engine, and all client/server containers are ready.

  4. Interact with the running emulation via the External Control API (see Runtime Control).


Configuration

All emulation parameters are centralized in config.yaml:

Constellation Parameters

ParameterDescriptionDefault
num_planesNumber of orbital planes72
sats_per_planeSatellites per orbital plane22
altitudeOrbital altitude (km)500
inclinationOrbital inclination (degrees)53
eccentricityOrbital eccentricity0
constellation_speedupTime speedup factor2.0
constellation_update_intervalLink update interval (ms)5000
initial_constellation_timestampEpoch for constellation definition[2025, 4, 24, 12, 0, 0]
initial_emulation_timestampStart time in constellation timescale[2025, 6, 1, 8, 0, 0]

Communication Parameters

ParameterDescriptionDefault
rate_islInter-satellite link rate (Mbps)1000
rate_uldlUplink/downlink rate (Mbps)100
min_elevation_cellMinimum elevation angle for cells (degrees)20
min_elevation_gwMinimum elevation angle for gateways (degrees)20
additional_per_hop_delayExtra per-hop delay (μs)3000
initial_rate_per_cellBackground traffic generated per UT/cell (Mbps)0.1

Scenario Files

FileDescription
user_loc.csvCell (UE) locations as lat,lng pairs. Default: 5,000 locations across the US.
gateways.csvGateway locations as lat,lng,num_antennas. Default: 54 gateways with 11 antennas each.
external_cell_ids.csvMapping of external client containers to cell IDs (see below).

Client Replicas & Cell Mapping

SERENADE distinguishes between background-traffic cells (all cells in the scenario, generating emulated dummy traffic) and external-traffic cells (a subset attached to real Docker client containers that carry genuine IP packets end-to-end).

The number of real client containers is set in docker-compose.yml:

services:
  client:
    deploy:
      mode: replicated
      replicas: 3   # <-- number of real-traffic client containers

Each client replica is mapped to a cell/UT by external_cell_ids.csv. The file lists one cell ID per line (after the header), and clients are assigned in order: serenade-client-1 🡒 first ID, serenade-client-2 🡒 second ID, and so on. If the number of IDs is less than the replica count, existing cells will be selected in order: serenade-client-3 🡒 first unassigned cell ID, serenade-client-4 🡒 second unassigned cell ID, and so on.

Real traffic from a client container enters the constellation at its mapped cell and shares the network with background traffic from all other cells. Cells mapped to external clients default to zero background traffic (so they only carry the real packets), but this can be overridden by setting enable_hybrid_traffic_per_cell: true in config.yaml.


Runtime Control

The External Control component provides a Python API client (api_client.py) for interacting with the emulation at runtime via REST:

from api_client import MyAPIClient

client = MyAPIClient(
    "http://serenade-serenade-1:8000",
    "http://serenade-constellation-engine-1:8000"
)

# Modify data rates for all cells
client.modify_all_rates(new_rate=1.0, apply_to_ext_clients=False)

# Modify data rate for specific cells
client.modify_these_rates(updates=[(2812, 0.5)])

# Relocate a UE
client.modify_these_locations(updates=[(2936, 40.4168, -3.7038)])

# Get current positions of all nodes
locations = client.get_locations()

# Run a controlled test-mode measurement
results = client.start_test_mode(test_rate=1.0, packet_count=2000)

# Reset the constellation engine
client.reset_constellation_engine()

Available Endpoints

MethodEndpointDescription
POST/modify-all-ratesSet the same data rate for all cells
POST/modify-these-ratesSet data rates for specific cells
GET/get-locationsRetrieve current UE, gateway, and satellite positions
POST/modify-these-locationsMove specific UEs to new coordinates
POST/start-test-modeRun a synchronized test across all cells and collect results

Extending SERENADE

Custom Assignment Algorithms

Replace or extend the Assignment Engine to implement your own cell🡘satellite🡘gateway assignment strategy. The engine communicates with SERENADE Core over HTTP — implement a server that accepts POST requests with the current node positions (ITRS coordinates) and returns per-cell [service_sat, feeder_sat, gateway] assignments.

Alternatively, provide precomputed assignments as a Parquet file (indexed by timestamp) and mount it into the assignment engine container.

Custom Traffic Generators

Replace the client/server containers with any application. As long as the client container routes traffic through the SERENADE Core (default gateway), all IP packets will transparently traverse the emulated constellation.

Scaling the Scenario


Project Structure

serenade/
├── config.yaml                  # Emulation configuration
├── docker-compose.yml           # Container orchestration
├── user_loc.csv                 # Cell/UE locations
├── gateways.csv                 # Gateway locations
├── external_cell_ids.csv        # Client-to-cell mapping

├── serenade/                    # Core emulation engine (Go)
│   ├── serenade.go              # Main entry point & orchestration loop
│   ├── satellite.go             # Satellite node: ISL routing, traffic handling
│   ├── cell.go                  # Cell node: UE-side traffic & assignment
│   ├── gateway.go               # Gateway node: backhaul & feeder link handling
│   ├── radiolinks.go            # Link computation: delays, visibility, ITRS
│   ├── comms.go                 # Communication infra: propagation channels, rate limiting
│   ├── packet.go                # Packet structure & next-hop logic
│   ├── externalinterface.go     # XDP/eBPF packet capture & injection
│   ├── externalcontrolserver.go # REST API server for runtime control
│   ├── constellation_adapter.go # HTTP client to Constellation Engine
│   ├── assignment_adapter.go    # HTTP client to Assignment Engine
│   ├── configuration.go         # Config loading (YAML + CSV)
│   ├── emulationnodes.go        # Node container struct
│   └── ebpf/                    # eBPF/XDP programs for packet filtering

├── constellation-engine/        # Satellite position server (Python/Skyfield)
├── assignment-engine/           # Cell assignment server (Python/Pandas)
├── external-control/            # Runtime control API client (Python)
├── client/                      # Traffic source container
├── server/                      # Traffic sink container
└── faux-dns/                    # DNS bootstrapping helper

License

This project is licensed under the PolyForm Noncommercial License 1.0.0. Free for research, education, and personal use; commercial use requires separate permission.