# Ella Core Documentation > A mobile core network for private deployments - easy to operate, reliable, and secure. A mobile core network for private deployments - easy to operate, reliable, and secure. # Home # Ella Core **Ella Core** is a mobile core designed for private networks. It simplifies the complexity of traditional mobile networks into a single, easy-to-operate solution that is reliable and secure. Use Ella Core where you need mobile connectivity: in a factory, a warehouse, a farm, a stadium, a ship, a military base, or a remote location. ## Key features - **Performant Data Plane**: Achieve high throughput and low latency with an eBPF-based data plane. Ella Core delivers over 10 Gbps of throughput and less than 1 ms of latency. - **Lightweight**: Ella Core is a single binary with an embedded database, making it easy and quick to stand up. It requires as little as 1 CPU core, 1GB of RAM, and 10GB of disk space. Forget specialized hardware; all you need to operate your mobile core network is a Linux system with a network interface. - **Highly Available**: Deploy Ella Core as a high-availability cluster to ensure continuous operation with failover capabilities. - **Subscriber Traffic Control**: Define permitted network flows per subscriber, enforce them in the user plane. Track subscriber traffic and usage in real time. - **AI-Native API**: Complete RESTful API and Go client for automation and integration. Manage every aspect of your network programmatically — or let AI agents do it securely using the OpenAPI specification and ready-to-use AI agent skill. - **BGP Support**: Advertise subscriber routes to your enterprise network and receive routes from BGP peers. - **Intuitive User Experience**: Manage subscribers, radios, data networks, policies, and operator information through a user-friendly embedded web interface. - **Real-Time Observability**: Access logs, metrics, traces, profiles, and dashboards to monitor network health through the UI, the Prometheus-compliant API, or an OpenTelemetry collector. - **Backup and Restore**: Backup and restore your data in 1 click. - **4G and 5G Compliant**: Ella Core implements 3GPP-standard interfaces and has been validated with multiple 4G and 5G radios, including integrated and software-defined RANs, commercial phones and devices. It is 5G RedCap compliant for IoT deployments. - **Audit Logs**: At any moment, keep track of who did what and when on your network. - **Source Available**: Ella Core is available under the Business Source License 1.1 (BUSL-1.1); each release converts to the Apache 2.0 license four years after publication. ## In this documentation - [**Tutorials**](https://docs.ellanetworks.com/tutorials/index.md) ______________________________________________________________________ **Start here**: a hands-on introduction to Ella Core for new users. Install, configure, and initialize Ella Core in minutes. - [**How-to Guides**](https://docs.ellanetworks.com/how_to/index.md) ______________________________________________________________________ **Step-by-step guides** covering key operation and common tasks. - [**Reference**](https://docs.ellanetworks.com/reference/index.md) ______________________________________________________________________ **Technical information** - API, configuration, performance, metrics, and more. - [**Explanation**](https://docs.ellanetworks.com/explanation/index.md) ______________________________________________________________________ **Discussion and clarification** of key topics. # Tutorials # Tutorials In these tutorials, we will learn how to deploy and operate an end-to-end mobile network with Ella Core. Choose the tutorial that best fits your setup: | Tutorial | Hardware Required | Installation Method | Duration | | ----------------------------------------------------------------------------------------------------------- | ----------------------------------- | ------------------- | -------- | | [Simulated 5G Network](https://docs.ellanetworks.com/tutorials/getting_started_simulation/index.md) | Any computer will do | Docker | ~10 min | | [5G Network with Radio Hardware](https://docs.ellanetworks.com/tutorials/getting_started_hardware/index.md) | Linux Host, 5G radio, UE, SIM cards | Snap | ~30 min | If you are new to Ella Core or don't have 5G hardware, start with the **Simulated 5G Network** tutorial. It uses UERANSIM to simulate a radio and user equipment, so you can experience the full end-to-end flow without any specialized hardware. If you have a 5G radio and user equipment, the **5G Network with Radio Hardware** tutorial walks you through a real-world deployment on bare metal using Snap. # Getting Started (5G Network with Radio Hardware) In this tutorial, we will deploy a complete end-to-end 5G network using Ella Core with a real 5G radio and user equipment. We will install Ella Core on bare metal using Snap, burn a SIM card, configure the network, and validate that a subscriber can connect and reach the internet. You can expect to spend about 30 minutes completing this tutorial. ## Pre-requisites To complete this tutorial, you will need the following: **Computer** - A Linux machine running Ubuntu 26.04 LTS - 2 network interfaces: - One for the radio connection (N2/N3 — control and user plane) - One for internet connectivity (N6 — data network) and the API/UI - See the full [system requirements](https://docs.ellanetworks.com/reference/system_reqs/index.md) **5G Equipment** - A [compatible 5G radio](https://docs.ellanetworks.com/reference/supported_mobile_equipment/#radios) - A [compatible 5G phone](https://docs.ellanetworks.com/reference/supported_mobile_equipment/#user-equipment) **SIM Card Provisioning** - Programmable SIM cards (e.g. [Sysmocom sysmoISIM-SJA5](https://sysmocom.de/products/sim/sysmoisim-sja5/index.html)) - A SIM card reader/writer (e.g. [HID OmniKey 3121](https://www.hidglobal.com/products/omnikey-3121)) - A SIM card programming tool (e.g. [pySim](https://github.com/osmocom/pysim)) installed on your machine ## 1. Install Ella Core Connect to the Linux machine where you will install Ella Core. Install the Ella Core snap and connect the required interfaces: ``` sudo snap install ella-core sudo snap connect ella-core:network-control sudo snap connect ella-core:process-control sudo snap connect ella-core:system-observe sudo snap connect ella-core:firewall-control sudo snap connect ella-core:mount-observe ``` Edit the configuration file: ``` sudo vim /var/snap/ella-core/common/core.yaml ``` Set the network interfaces to match your system. In this example, `ens5` is connected to the radio and `ens3` is connected to the internet: /var/snap/ella-core/common/core.yaml ``` logging: system: level: "info" output: "stdout" audit: output: "stdout" db: path: "/var/snap/ella-core/common/data/ella.db" interfaces: n2: name: "ens5" ngap-port: 38412 n3: name: "ens5" n6: name: "ens3" api: address: "0.0.0.0" port: 5002 datapath: attach-mode: "xdp-native" ``` Note Replace `ens5` and `ens3` with the actual interface names on your machine. N2 and N3 should point to the interface connected to the radio. N6 should point to the interface connected to the internet. See the [configuration reference](https://docs.ellanetworks.com/reference/config_file/index.md) for all available options. Start Ella Core: ``` sudo snap start --enable ella-core.cored ``` ## 2. Configure a Subscriber in Ella Core Open your browser and navigate to `https://:5002/` to access Ella Core's UI. You should see the Initialization page. Create the first user with the following credentials: - Email: `admin@ellanetworks.com` - Password: `admin` Ella Core is now initialized. You will be redirected to the dashboard. Navigate to the `Subscribers` page and click on the `Create` button. Create a subscriber with the following parameters: - **IMSI**: Click on the `Generate` button to create a random IMSI - **Key**: Click on the `Generate` button to create a random key - **Sequence Number**: Keep the default value - **Profile**: Keep the default value Take note of the **IMSI**, **Key**, and **OPC** values. You will need them to burn the SIM card. ## 3. Burn the SIM Card Insert a blank programmable SIM card into your card reader. Use pySim to program the SIM card with the subscriber credentials from the previous step. Replace the `IMSI`, `KEY`, and `OPC` values below with the ones you noted: ``` export IMSI= export KEY= export OPC= export MCC=001 export MNC=01 export ADMIN_CODE= ./pySim-prog.py -p0 -n Ella -t sysmoISIM-SJA5 -i $IMSI -c $MCC -x $MCC -y $MNC -o $OPC -k $KEY -a $ADMIN_CODE -j 1 ``` Note The `MCC` and `MNC` values match Ella Core's Operator configuration. Note The `ADMIN_CODE` is specific to your SIM card vendor. Note Some devices (e.g. iPhones) require additional SUCI configuration on the SIM card. See [Managing SIM Cards](https://docs.ellanetworks.com/explanation/managing_sim_cards/index.md) for details. Insert the programmed SIM card into your user equipment. ## 4. Connect the Radio Configure your 5G radio to connect to Ella Core. You will need to set: - **AMF Address**: The IP address of your radio interface. Run `ip addr show ens5` to find it (replace `ens5` with your radio interface name). - **PLMN ID**: `001-01` (MCC-MNC matching Ella Core's Operator configuration) - **TAC**: `000001` (TAC matching Ella Core's Operator configuration) - **SST/SD**: `1/NULL` (Slice configuration matching Ella Core's Operator configuration) Power on the radio. For detailed instructions, see [Integrate with a Radio](https://docs.ellanetworks.com/how_to/integrate_with_radio/index.md). In the Ella Core UI, navigate to the `Radios` page. You should see your radio appear as connected. ## 5. Connect the User Equipment Power on the user equipment with the programmed SIM card inserted. Set the APN to `internet` (matching Ella Core's default Data Network). The device should automatically search for and connect to your network. In the Ella Core UI, navigate to the `Subscribers` page. You should see that your subscriber has been assigned an IP address, confirming a successful data session establishment. From the user equipment, try to access the internet (e.g. open a web browser and navigate to any website, or ping an external address). Success Congratulations! You have deployed a complete end-to-end 5G network with real hardware. Your subscriber is connected through Ella Core and can reach the internet. # Getting Started (Simulated 5G Network) In this tutorial, we will deploy a complete end-to-end 5G network using Ella Core and UERANSIM (a 5G radio and user equipment simulator). By the end, a simulated subscriber will be connected to your network and able to reach the internet. No 5G radio or specialized hardware is required. You can expect to spend about 10 minutes completing this tutorial. ## Pre-requisites To complete this tutorial, you will need a Linux machine with [Docker](https://www.docker.com/) installed. ## 1. Install Ella Core and UERANSIM Create a new directory for this tutorial and navigate into it: ``` mkdir ella cd ella ``` Copy the following file into this directory: docker-compose.yaml ``` services: ella-core: image: ghcr.io/ellanetworks/ella-core:v1.15.0 restart: unless-stopped entrypoint: /bin/core --config /core.yaml privileged: true ports: - "5002:5002" networks: default: driver_opts: com.docker.network.endpoint.ifname: eth0 n3: driver_opts: com.docker.network.endpoint.ifname: n3 ipv4_address: 10.3.0.2 ueransim: image: ghcr.io/ellanetworks/ueransim:3.2.7 restart: unless-stopped privileged: true networks: n3: driver_opts: com.docker.network.endpoint.ifname: n3 ipv4_address: 10.3.0.3 networks: n3: internal: true ipam: config: - subnet: 10.3.0.0/24 ``` Start the Ella Core and UERANSIM containers: ``` docker compose up -d ``` You should see the following output: ``` [+] Running 4/4 ✔ Network ella_default Created ✔ Network ella_n3 Created ✔ Container ella-ella-core-1 Started ✔ Container ella-ueransim-1 Started ``` ## 2. Initialize Ella Core Open your browser and navigate to `http://127.0.0.1:5002/` to access Ella Core's UI. You should see the Initialization page. Note Your browser may display a warning about the security of the connection. You can safely ignore this warning. Create the first user with the following credentials: - Email: `admin@ellanetworks.com` - Password: `admin` Ella Core is now initialized. You will be redirected to the dashboard. ## 3. Create a Subscriber Navigate to the `Subscribers` page and click on the `Create` button. Create a subscriber with the following parameters: - IMSI: `001019756139935` - Key: `0eefb0893e6f1c2855a3a244c6db1277` - Sequence Number: Keep the default value. - OPC: Select "Provide custom OPC" and set the value to `98da19bbc55e2a5b53857d10557b1d26`. - Profile: Keep the default value. ## 4. Connect the 5G Radio Simulator Go back to your terminal, in the same directory where you created the `docker-compose.yaml` file. Start the 5G radio simulator: ``` docker compose exec -ti ueransim bin/nr-gnb --config /gnb.yaml ``` You should see the following output: ``` UERANSIM v3.2.7 [2025-10-24 17:46:43.402] [sctp] [info] Trying to establish SCTP connection... (10.3.0.2:38412) [2025-10-24 17:46:43.404] [sctp] [info] SCTP connection established (10.3.0.2:38412) [2025-10-24 17:46:43.404] [sctp] [debug] SCTP association setup ascId[281] [2025-10-24 17:46:43.404] [ngap] [debug] Sending NG Setup Request [2025-10-24 17:46:43.405] [ngap] [debug] NG Setup Response received [2025-10-24 17:46:43.405] [ngap] [info] NG Setup procedure is successful ``` Leave the radio running, don't close the terminal. In your browser, navigate to the Ella Core UI and click on the `Radios` tab. You should see a radio connected with the name `UERANSIM-gnb-1-1-1`. ## 5. Connect the User Equipment Simulator Open a new terminal window in the same directory where you created the `docker-compose.yaml` file. Start the UE simulator: ``` docker compose exec -ti ueransim bin/nr-ue --config /ue.yaml ``` You should see the following output: ``` UERANSIM v3.2.7 [2025-10-24 17:51:25.972] [nas] [info] UE switches to state [MM-DEREGISTERED/PLMN-SEARCH] [2025-10-24 17:51:25.973] [rrc] [debug] New signal detected for cell[1], total [1] cells in coverage [2025-10-24 17:51:25.973] [nas] [info] Selected plmn[001/01] [2025-10-24 17:51:25.973] [rrc] [info] Selected cell plmn[001/01] tac[1] category[SUITABLE] [2025-10-24 17:51:25.973] [nas] [info] UE switches to state [MM-DEREGISTERED/PS] [2025-10-24 17:51:25.973] [nas] [info] UE switches to state [MM-DEREGISTERED/NORMAL-SERVICE] [2025-10-24 17:51:25.973] [nas] [debug] Initial registration required due to [MM-DEREG-NORMAL-SERVICE] [2025-10-24 17:51:25.973] [nas] [debug] UAC access attempt is allowed for identity[0], category[MO_sig] [2025-10-24 17:51:25.973] [nas] [debug] Sending Initial Registration [2025-10-24 17:51:25.973] [nas] [info] UE switches to state [MM-REGISTER-INITIATED] [2025-10-24 17:51:25.973] [rrc] [debug] Sending RRC Setup Request [2025-10-24 17:51:25.973] [rrc] [info] RRC connection established [2025-10-24 17:51:25.973] [rrc] [info] UE switches to state [RRC-CONNECTED] [2025-10-24 17:51:25.973] [nas] [info] UE switches to state [CM-CONNECTED] [2025-10-24 17:51:25.975] [nas] [debug] Authentication Request received [2025-10-24 17:51:25.975] [nas] [debug] Received SQN [000000000022] [2025-10-24 17:51:25.975] [nas] [debug] SQN-MS [000000000000] [2025-10-24 17:51:25.976] [nas] [debug] Security Mode Command received [2025-10-24 17:51:25.976] [nas] [debug] Selected integrity[1] ciphering[0] [2025-10-24 17:51:25.978] [nas] [debug] Registration accept received [2025-10-24 17:51:25.978] [nas] [info] UE switches to state [MM-REGISTERED/NORMAL-SERVICE] [2025-10-24 17:51:25.978] [nas] [debug] Sending Registration Complete [2025-10-24 17:51:25.978] [nas] [info] Initial Registration is successful [2025-10-24 17:51:25.978] [nas] [debug] Sending PDU Session Establishment Request [2025-10-24 17:51:25.978] [nas] [debug] UAC access attempt is allowed for identity[0], category[MO_sig] [2025-10-24 17:51:26.187] [nas] [debug] PDU Session Establishment Accept received [2025-10-24 17:51:26.187] [nas] [info] PDU Session establishment is successful PSI[1] [2025-10-24 17:51:26.211] [app] [info] Connection setup for PDU session[1] is successful, TUN interface[uesimtun0, 10.45.0.1] is up. ``` The User Equipment has successfully connected to the network and has been assigned the IP address `10.45.0.1`. Leave the UE running, don't close the terminal. ## 6. Validate the Connection In your browser, navigate to the Ella Core UI and click on the `Subscribers` tab. You should see that the subscriber you created has been assigned an IP address matching the one from the UE output. Open a new terminal window in the same directory where you created the `docker-compose.yaml` file. Ping Google's DNS server from the subscriber's interface: ``` docker compose exec -ti ueransim ping -I uesimtun0 8.8.8.8 -c4 ``` You should see a successful ping: ``` PING 8.8.8.8 (8.8.8.8) from 10.45.0.1 uesimtun0: 56(84) bytes of data. 64 bytes from 8.8.8.8: icmp_seq=1 ttl=116 time=39.0 ms 64 bytes from 8.8.8.8: icmp_seq=2 ttl=116 time=37.9 ms 64 bytes from 8.8.8.8: icmp_seq=3 ttl=116 time=37.4 ms 64 bytes from 8.8.8.8: icmp_seq=4 ttl=116 time=18.9 ms --- 8.8.8.8 ping statistics --- 4 packets transmitted, 4 received, 0% packet loss, time 3003ms rtt min/avg/max/mdev = 18.865/33.300/39.038/8.355 ms ``` Success Congratulations! You have deployed a complete end-to-end 5G network. A simulated subscriber is connected and can reach the internet through Ella Core. ## 7. Clean Up (Optional) When you are done with the tutorial, you can remove the containers and the networks: ``` docker compose down ``` # How-to Guides # How-to Guides These how-to guides cover key operations and common tasks in Ella Core. ## Day 1 Operations Initial setup and configuration of Ella Core. - [Install](https://docs.ellanetworks.com/how_to/install/index.md) - [Deploy a High Availability Cluster](https://docs.ellanetworks.com/how_to/deploy_ha_cluster/index.md) - [Co-host with OCUDU](https://docs.ellanetworks.com/how_to/co_host_with_ocudu/index.md) - [Use VLANs](https://docs.ellanetworks.com/how_to/vlan/index.md) - [Use native XDP with veth interfaces](https://docs.ellanetworks.com/how_to/native_xdp_veth/index.md) - [Disable Merged Packets](https://docs.ellanetworks.com/how_to/disable_merged_packets/index.md) - [Integrate with a Radio](https://docs.ellanetworks.com/how_to/integrate_with_radio/index.md) ## Day 2 Operations Ongoing management and maintenance of a running Ella Core deployment. - [Backup and Restore](https://docs.ellanetworks.com/how_to/backup_and_restore/index.md) - [Scale Up a High Availability Cluster](https://docs.ellanetworks.com/how_to/scale_up_ha_cluster/index.md) - [Perform a Rolling Upgrade](https://docs.ellanetworks.com/how_to/rolling_upgrade/index.md) - [Tune Network Performance](https://docs.ellanetworks.com/how_to/performance_tuning/index.md) - [Advertise Subscriber Routes with BGP](https://docs.ellanetworks.com/how_to/bgp/index.md) - [Manage Your Network with AI Agents](https://docs.ellanetworks.com/how_to/ai_agents/index.md) # Manage Your Network with AI Agents Ella Core ships with an [Agent Skill](https://agentskills.io/) that lets AI agents manage your mobile network using natural language. The skill provides the OpenAPI specification so agents can discover and call the REST API on your behalf. ## Prerequisites Before using the skill, you need: 1. **A running Ella Core instance** with its API accessible (e.g. `http://192.168.1.10:5000`). 1. **A user for your AI agent with an API token** — create a user for your agent in the UI with a role that matches the permissions you want to grant (e.g. "network manager" for full network access, "read only" for monitoring). Then generate an API token for that user and copy it. ## 1. Install the skill ``` /plugin marketplace add ellanetworks/core /plugin install ella-core@ellanetworks-core ``` Refresh later with `/plugin marketplace update`. Works with any Skills-aware tool that auto-discovers `.agents/skills/` (Codex, Cursor, Cline, Goose, Amp, OpenCode, …). ``` npx skills add ellanetworks/core ``` Copy the [`skills/ella-core/`](https://github.com/ellanetworks/core/tree/main/skills/ella-core) folder into the skills directory your AI tool discovers. ## 2. Prompt the agent Once the skill is active, you can ask things like "Which subscribers used the most data over the last 7 days?". The agent will ask you for the Ella Core URL and an API token — use the token you generated earlier. Claude Opus responds to "Which subscribers used the most data over the last 7 days?" using its Ella Core skill. # Backup and Restore Ella Core stores all persistent data in an embedded database. You can create backups of this database to protect your data and restore it in case of data loss. 1. Open Ella Core in your web browser. 1. Navigate to the **Backup and Restore** tab in the left-hand menu. 1. Click on the **Backup** button. 1. The backup file will be downloaded to your computer. Store this file in a safe location. Warning The backup archive contains sensitive secrets. Store and transfer it encrypted, and treat it as you would an admin credential. Note This operation can also be done using the API. Please see the [backup API documentation](https://docs.ellanetworks.com/reference/api/backup/index.md) for more information. Warning Restoring a backup will overwrite all existing data in your Ella Core installation. This path is **disabled in HA mode**. Clustered deployments use the disaster-recovery flow described below. On a new installation of Ella Core, you can restore a backup to recover your data. 1. Open Ella Core in your web browser. 1. Navigate to the **Backup and Restore** tab in the left-hand menu. 1. Click on the **Upload File** button. 1. Select the backup file you want to restore. Note This operation can also be done using the API. Please see the [restore API documentation](https://docs.ellanetworks.com/reference/api/restore/index.md) for more information. ## Disaster recovery for HA clusters 1. Stop every voter in the cluster. 1. On one node, drop the backup archive into the data directory as `restore.bundle`: ``` sudo mv backup.tar.gz /var/snap/ella-core/common/restore.bundle sudo chmod 600 /var/snap/ella-core/common/restore.bundle ``` 1. Start the daemon on that node: ``` sudo snap start --enable ella-core.cored ``` 1. Add the remaining nodes via the [join-token flow](https://docs.ellanetworks.com/how_to/deploy_ha_cluster/index.md). Ella Core includes an embedded BGP speaker that advertises `/32` (IPv4) or `/64` (IPv6) routes for each active subscriber. This guide walks through enabling BGP, adding a peer, and verifying the configuration. For background on how Ella Core uses BGP, see the [BGP Route Advertisement](https://docs.ellanetworks.com/explanation/bgp/index.md) explanation. NAT must be disabled NAT and BGP are mutually exclusive. Disable NAT from the Networking > NAT tab before proceeding. ## Enable BGP 1. Open the Ella Core UI and navigate to **Networking > BGP**. 1. Edit the BGP settings: - **Local AS**: Your autonomous system number (e.g. `64512`). - **Router ID**: A unique IP address identifying this BGP speaker, typically the N6 interface IP (e.g. `192.168.5.10`). - **Listen Address**: The address and port to listen on (default `:179`). Change this only if you need BGP on a non-standard port. 1. Click **Save**. 1. Toggle **BGP** to **ON**. ## Add a BGP peer 1. In the **Networking > BGP** tab, scroll to the **Peers** section. 1. Click **Create**. 1. Fill in the peer details: - **Address**: The IP address of the upstream router (e.g. `192.168.5.1` or `2001:db8::1`). - **Remote AS**: The AS number of the peer (e.g. `64513`). - **Hold Time**: BGP hold timer in seconds (default `90`). The keepalive interval is derived as hold time / 3 per RFC 4271. - **Password** (optional): MD5 authentication password. Must match the peer's configuration. - **Description** (optional): A label for the peer. 1. Click **Create**. ## Verify advertised routes View the advertised subscriber routes in the **Advertised Routes** table. The next-hop is always the N6 interface address. ## Configure the upstream router Ella Core advertises routes, and can receive them from peers via each peer's import prefixes. You still need to configure the upstream router to: 1. Peer with Ella Core (matching the AS number and address configured above). 1. Accept all routes for subscriber data networks (e.g. `192.168.0.0/24` or `2001:db8::/32`) 1. Accept framed-route prefixes, if any are configured (e.g. `192.168.60.0/24`). Consult your router's documentation for BGP peering configuration. Note All steps in this guide can also be performed via the REST API. See the [BGP API reference](https://docs.ellanetworks.com/reference/api/networking/#bgp) for details. # Co-host with OCUDU Ella Core can be hosted with radio software like [OCUDU](https://ocudu.org/) (previously known as srsRAN) to operate an all-in-one private mobile network. This guide provides step-by-step instructions to deploy Ella Core alongside OCUDU using a Linux network namespace. Co-host Ella Core with OCUDU ## Pre-requisites To follow this guide, you will need: - A host with a network interface - An OCUDU-compatible SDR The instructions below were written for a Raspberry Pi 5 running Ubuntu 24.04 as the host and the Ettus Research B205-mini as the SDR. Please adapt the interface names and SDR configuration as needed for your setup. Tip OCUDU requires some performance tuning for stable operation, especially on resource-constrained hosts like a Raspberry Pi. We recommend the following optimizations for OCUDU performance: - Use Ubuntu Real-Time (with [pro client](https://documentation.ubuntu.com/pro-client/en/latest/howtoguides/enable_realtime_kernel/#enable-and-install-automatically)) - Use a high real-time scheduling priority (with [chrt](https://man7.org/linux/man-pages/man1/chrt.1.html)) - Set scaling governor to Performance (with [OCUDU performance script](https://gitlab.com/ocudu/ocudu/-/blob/dev/scripts/ocudu_performance?ref_type=heads)) - Disable DRM KMS polling (with [OCUDU performance script](https://gitlab.com/ocudu/ocudu/-/blob/dev/scripts/ocudu_performance?ref_type=heads)) ## 1. Install Ella Core and OCUDU Install Ella Core using the [How-to Install guide](https://docs.ellanetworks.com/how_to/install/index.md) and install OCUDU using the [official documentation](https://ocudu.gitlab.io/ocudu_docs/user_manual/installation/). ## 2. Create a network namespace for N3 Create a linux network namespace `n3ns` for the N3 interface between OCUDU and Ella Core. ``` ip netns add n3ns ip link add n3-upf-veth type veth peer name n3-ran-veth ip link set n3-ran-veth netns n3ns ip addr add 10.202.0.3/24 dev n3-upf-veth ip -n n3ns addr add 10.202.0.5/24 dev n3-ran-veth ip -n n3ns link set lo up ip -n n3ns link set dev n3-ran-veth up ip link set dev n3-upf-veth up ethtool -K eth0 gro off ip netns exec n3ns ethtool -K n3-ran-veth tso off gso off ``` `tcx` mode requires both interfaces to deliver unmerged packets — see [Disable merged packets](https://docs.ellanetworks.com/how_to/disable_merged_packets/index.md). ## 3. Configure Ella Core Configure Ella Core's N3 and N3 interfaces to use the `n3ns` namespace and set N6 to the physical interface `eth0`: ``` logging: system: level: "debug" output: "stdout" audit: output: "stdout" db: path: "/var/snap/ella-core/common/data/ella.db" interfaces: n2: name: "n3-upf-veth" ngap-port: 38412 n3: name: "n3-upf-veth" n6: name: "eth0" api: address: "0.0.0.0" port: 5002 datapath: attach-mode: "tcx" telemetry: enabled: false ``` Note We use `tcx` mode here because the Raspberry Pi 5's built-in NIC does not support native XDP. If your host's NIC supports native XDP, set `attach-mode` to `xdp-native` and follow the [Use native XDP with veth interfaces](https://docs.ellanetworks.com/how_to/native_xdp_veth/index.md) guide to attach an XDP program to the peer veth. Start Ella Core: ``` sudo snap start ella-core ``` ## 4. Configure OCUDU Configure OCUDU's CU to use the `n3ns` namespace: ``` cu_cp: amf: addr: 10.202.0.3 port: 38412 bind_addr: 10.202.0.5 supported_tracking_areas: - tac: 1 plmn_list: - plmn: "99901" tai_slice_support_list: - sst: 1 inactivity_timer: 300 security: nea_pref_list: nea2,nea1 nia_pref_list: nia2,nia1 cu_up: ngu: socket: - bind_addr: 10.202.0.5 ru_sdr: device_driver: uhd device_args: type=b200 clock: internal srate: 23.04 tx_gain: 80 rx_gain: 40 cell_cfg: dl_arfcn: 665000 band: 77 channel_bandwidth_MHz: 20 common_scs: 30 plmn: "99901" tac: 1 pdcch: dedicated: ss2_type: common dci_format_0_1_and_1_1: false prach: prach_config_index: 160 pdsch: mcs_table: qam64 pusch: mcs_table: qam64 log: filename: /tmp/gnb.log all_level: warning pcap: mac_enable: disable ngap_enable: disable ``` Start OCUDU in the `n3ns` namespace: ``` sudo ip netns exec n3ns ./gnb -c gnb.yaml ``` You should see OCUDU logs indicating successful connection to Ella Core ``` --== OCUDU gNB (commit d1ca6d2744) ==-- 2026-02-16T18:34:35.963576 [GNB ] [I] Built in Release mode using commit d1ca6d2744 on branch dev Lower PHY in dual baseband executor mode. Available radio types: uhd. [INFO] [UHD] linux; GNU C++ version 13.2.0; Boost_108300; UHD_4.6.0.0+ds1-5.1ubuntu0.24.04.1 Making USRP object with args 'type=b200' [INFO] [LOGGING] Fastpath logging disabled at runtime. [INFO] [B200] Detected Device: B205mini [INFO] [B200] Operating over USB 3. [INFO] [B200] Initialize CODEC control... [INFO] [B200] Initialize Radio control... [INFO] [B200] Performing register loopback test... [INFO] [B200] Register loopback test passed [INFO] [B200] Setting master clock rate selection to 'automatic'. [INFO] [B200] Asking for clock rate 16.000000 MHz... [INFO] [B200] Actually got clock rate 16.000000 MHz. [INFO] [MULTI_USRP] Setting master clock rate selection to 'manual'. [INFO] [B200] Asking for clock rate 23.040000 MHz... [INFO] [B200] Actually got clock rate 23.040000 MHz. Cell pci=1, bw=20 MHz, 1T1R, dl_arfcn=665000 (n77), dl_freq=3975 MHz, dl_ssb_arfcn=664704, ul_freq=3975 MHz N2: Connection to AMF on 10.202.0.3:38412 completed Remote control server listening on 0.0.0.0:8001 ==== gNB started === ``` # Deploy a High Availability Cluster Ella Core can be deployed as a high-availability cluster to provide redundancy and failover capabilities. See [High Availability](https://docs.ellanetworks.com/explanation/high_availability/index.md) for more information. ## Prerequisites - Three hosts meeting the standard [system requirements](https://docs.ellanetworks.com/reference/system_reqs/index.md). - Ella Core installed on each host via the [Install](https://docs.ellanetworks.com/how_to/install/index.md) guide. Do **not** start the service yet. - A reachable TCP port on each host for inter-node traffic (this guide uses `7000`). ## 1. Configure node 1 Put this in `core.yaml` on node 1. Adjust interface names, addresses, and ports to match the host. core.yaml (node 1) ``` logging: system: level: "info" output: "stdout" audit: output: "stdout" db: path: "/var/snap/ella-core/common/ella.db" interfaces: n2: address: "10.0.0.1" ngap-port: 38412 n3: name: "n3" n6: name: "eth0" api: address: "10.0.0.1" port: 5002 datapath: attach-mode: "xdp-native" cluster: enabled: true node-id: 1 bind-address: "10.0.0.1:7000" peers: - "10.0.0.1:7000" - "10.0.0.2:7000" - "10.0.0.3:7000" ``` ## 2. Start node 1 ``` sudo snap start --enable ella-core.cored ``` ## 3. Create the admin user Open `https://10.0.0.1:5002` in a browser, create the admin, and log in. ## 4. Add node 2 On node 1, open the **Cluster** page and click **Add Node**. Select node ID `2`, click **Mint Token**, then copy the token. Create `core.yaml` on node 2 using the same shape as node 1, with `bind-address: "10.0.0.2:7000"`. Paste the copied token block over the placeholder: core.yaml (node 2, cluster block) ``` cluster: enabled: true node-id: 2 bind-address: "10.0.0.2:7000" peers: - "10.0.0.1:7000" - "10.0.0.2:7000" - "10.0.0.3:7000" join-token: "ejYM..." ``` Start node 2: ``` sudo snap start --enable ella-core.cored ``` ## 5. Add node 3 Repeat step 4 on node 3. ## 6. Verify On the **Cluster** page, all three nodes appear as **Voter**, one as **Leader**, all **Healthy**. # Disable merged packets When installing Ella Core in `tcx` or `xdp-generic` datapath mode, no merged packets should reach the data plane (see the [merged packets explanation](https://docs.ellanetworks.com/explanation/user_plane_packet_processing_with_ebpf/#merged-packets) for more info). Apply this to both the N3 and the N6 interface. Depending on the deployment: **Physical NIC**: ``` ethtool -K gro off lro off ``` **veth**: ``` ethtool -K tso off gso off ``` **virtio (VM)**: ``` ethtool -K gro off rx-gro-hw off ``` # Install Ensure your system meets the [requirements](https://docs.ellanetworks.com/reference/system_reqs/index.md). Then, choose one of the installation methods below. Install the Ella Core snap and connect it to the required interfaces: ``` sudo snap install ella-core sudo snap connect ella-core:network-control sudo snap connect ella-core:process-control sudo snap connect ella-core:system-observe sudo snap connect ella-core:firewall-control sudo snap connect ella-core:mount-observe ``` Configure Ella Core: ``` sudo vim /var/snap/ella-core/common/core.yaml ``` Start Ella Core: ``` sudo snap start --enable ella-core.cored ``` Install the required dependencies: ``` sudo snap install go --channel=1.26/stable --classic sudo snap install node --channel=24/stable --classic sudo apt update sudo apt -y install clang llvm gcc-multilib libbpf-dev ``` Clone the Ella Core repository: ``` git clone https://github.com/ellanetworks/core.git cd core ``` Build the frontend: ``` npm install --prefix ui npm run build --prefix ui ``` Build Ella Core: ``` REVISION=`git rev-parse HEAD` go build -ldflags "-X github.com/ellanetworks/core/version.GitCommit=${REVISION}" ./cmd/core/main.go ``` Configure Ella Core: ``` vim core.yaml ``` Start Ella Core: ``` sudo ./main -config core.yaml ``` Create a new directory: ``` mkdir ella cd ella ``` Copy the following file into this directory: docker-compose.yaml ``` configs: ella_config: content: | logging: system: level: "info" output: "stdout" audit: output: "stdout" db: path: "data" interfaces: n2: address: "10.3.0.2" ngap-port: 38412 n3: name: "n3" n6: name: "eth0" api: address: "0.0.0.0" port: 5002 datapath: attach-mode: "tcx" services: ella-core: image: ghcr.io/ellanetworks/ella-core:v1.15.0 configs: - source: ella_config target: /core.yaml restart: unless-stopped entrypoint: /bin/core --config /core.yaml privileged: true ports: - "5002:5002" networks: default: driver_opts: com.docker.network.endpoint.ifname: eth0 n3: driver_opts: com.docker.network.endpoint.ifname: n3 ipv4_address: 10.3.0.2 networks: n3: internal: true ipam: config: - subnet: 10.3.0.0/24 ``` Edit the file to match your network interfaces and desired configuration. Note This example uses `tcx` mode, which works on the veth interfaces containers get. On a host whose driver supports it, use `xdp-native` mode for the highest performance — see [Config file](https://docs.ellanetworks.com/reference/config_file/index.md). Start the Ella Core container: ``` docker compose up -d ``` Ensure your Kubernetes cluster is running with the [Multus CNI](https://github.com/k8snetworkplumbingwg/multus-cni) installed. ``` kubectl apply -k github.com/ellanetworks/core/k8s?ref=v1.15.0 -n ella ``` # Integrate with a Radio Radios are automatically added to Ella Core as they connect to the network as long as they are configured to use the same Operator information as Ella Core. Follow this guide to integrate Ella Core with a radio. This guide assumes you have already deployed Ella Core. ## 1. Configure the Operator information 1. Open Ella Core in your web browser. 1. Click on the **Operator** tab in the left-hand menu. 1. Edit the Operator information: - **MCC**: The Mobile Country Code for the operator. - **MNC**: The Mobile Network Code for the operator. - **Supported TACs**: A list of supported Tracking Area Codes (TACs). ## 2. Configure the radio In your radio's configuration, you will likely need to specify the following information to connect it with a mobile core network: - **AMF / MME Address**: The address of the N2 / S1-MME interface on Ella Core. - **PLMN ID**: The Public Land Mobile Network Identifier. This is a combination of the Mobile Country Code (MCC) and the Mobile Network Code (MNC). You can find this information in Ella Core under **Operator** and **Operator ID**. - **TAC**: The Tracking Area Code. You can find this information in Ella Core under **Operator** and **Supported TACs**. - **SST (5G only)**: The Slice/Service Type. Identifies a network slice. You can find this information in Ella Core under **Networking** and **Slices**. - **SD (5G only)**: The Slice Differentiator. Optionally differentiates slices sharing the same SST. You can find this information in Ella Core under **Networking** and **Slices**. Note Each radio has its own configuration interface. Consult the radio's documentation for specific instructions. # Use native XDP with veth interfaces When Ella Core's N3 or N6 interface is a veth pair and XDP is set to `native` mode, you must attach a minimal XDP program to the peer side of the veth. Without it, downlink traffic will silently fail. For an explanation of why this is needed, see [XDP redirect on veth pairs](https://docs.ellanetworks.com/explanation/user_plane_packet_processing_with_ebpf/#xdp-redirect-on-veth-pairs). ## 1. Install prerequisites Install the BPF toolchain needed to compile the XDP program: ``` sudo apt update sudo apt install -y clang llvm libbpf-dev linux-headers-$(uname -r) ``` ## 2. Create the zero entrypoint program Create a file called `zero_entrypoint.c` with the following content: ``` #include #include SEC("xdp") int zero_entrypoint(struct xdp_md *ctx) { return XDP_PASS; } char _license[] SEC("license") = "GPL"; ``` ## 3. Compile the program ``` clang -O2 -g -Wall -target bpf -c zero_entrypoint.c -o zero_entrypoint.o ``` ## 4. Attach the program to the peer veth Attach the compiled program to the peer veth interface. If the peer is in a network namespace (e.g. `n3ns`), use `ip netns exec`: ``` sudo ip netns exec n3ns ip link set dev n3-ran-veth xdpgeneric off sudo ip netns exec n3ns ip link set dev n3-ran-veth xdp obj zero_entrypoint.o sec xdp ``` If the peer is not in a namespace: ``` sudo ip link set dev xdp obj zero_entrypoint.o sec xdp ``` ## 5. Verify Confirm the program is attached: ``` sudo ip netns exec n3ns ip link show n3-ran-veth ``` You should see `prog/xdp` in the output. ## 6. Configure Ella Core for native XDP Set the attach mode to `xdp-native` in the Ella Core configuration file: ``` datapath: attach-mode: "xdp-native" ``` Restart Ella Core for the change to take effect. This guide describes the steps and settings required to tune network performance for Ella Core. It was written with 10Gbps NICs in mind. The exact values required will depend on the hardware used. ## Configure transmit queue length Increase the transmit queue length to 10000 by adding the following line to `/etc/udev/rules.d/60-ella-core.rules` for each interfaces handling N3 or N6 traffic: ``` KERNEL=="enp4s0f0", RUN+="/sbin/ip link set %k txqueuelen 10000" ``` ## Configure multiqueue For all interfaces handling N3 or N6 traffic, configure multiple queues. The appropriate number of queues is the larger between the number of cores and the maximum number of queues supported by the NIC: ``` nic=enp4s0f0 nproc=$(nproc) max_allowed=$(sudo ethtool -l $nic | grep Combined | awk '{ print $2 }' | sort -n | head -n 1) if [ $nproc -ge $max_allowed ]; then echo $nproc else echo $max_allowed fi ``` Assuming the above script output was 16, add the following line to `/etc/udev/rules.d/60-ella-core.rules`: ``` KERNEL=="enp4s0f0", RUN+="/usr/sbin/ethtool -L %k combined 16" ``` ## Increase the NIC ring buffer sizes Find the maximum allowed ring buffer sizes allowed by your hardware: ``` sudo ethtool -g enp4s0f0 ``` The output should be similar to this: ``` Ring parameters for enp4s0f0: Pre-set maximums: RX: 8192 RX Mini: n/a RX Jumbo: n/a TX: 8192 TX push buff len: n/a Current hardware settings: RX: 512 RX Mini: n/a RX Jumbo: n/a TX: 512 RX Buf Len: n/a CQE Size: n/a TX Push: off RX Push: off TX push buff len: n/a TCP data split: n/a ``` Add the following line to `/etc/udev/rules.d/60-ella-core.rules`, using the values from the `Pre-set maximums` section: ``` KERNEL=="enp4s0f0", RUN+="/usr/sbin/ethtool -G %k rx 8192 tx 8192" ``` ## Tune maximum backlog in the receive queues Increase the maximum backlog in receive queues by adding the following content to `/etc/sysctl.d/99-ella-core.conf`: ``` # increase the maximum backlog net.core.netdev_max_backlog = 182757 ``` ## Apply the changes The changes in this guide can be applied by either rebooting, or running the following commands: ``` sudo udevadm trigger sudo sysctl --system ``` # Perform a Rolling Upgrade This guide walks through upgrading every node in a running Ella Core high-availability cluster, one at a time, without taking the cluster offline. For background on mixed-version clusters, draining, and schema coordination, see [High Availability](https://docs.ellanetworks.com/explanation/high_availability/index.md). ## Prerequisites - A running cluster deployed via [Deploy a High Availability Cluster](https://docs.ellanetworks.com/how_to/deploy_ha_cluster/index.md). - Admin credentials for the Ella Core UI, or an admin API token. - The target Ella Core version available on the snap channel you track. ## Upgrade one node Repeat these steps for each node, **upgrading the leader last**. 1. Identify the leader on the **Cluster** page of any healthy node. 1. Pick the next node to upgrade — a follower, unless this is the last pass. 1. Click **Drain** next to that node. Wait until its **Drain State** is `drained`. 1. On that host, refresh the snap: ``` sudo snap refresh ella-core ``` 1. On the **Cluster** page, wait for the node to return to **Healthy**. 1. Click **Resume** next to the node. Wait for **Drain State** to clear back to `active`. 1. Move to the next node. ## Verify the upgrade After every node has been refreshed, open the **Cluster** page and confirm: - Every node's **Version** column shows the target release. - The mixed-version warning banner is gone. - Every node is **Healthy** and its **Drain State** is `active`. Note All steps in this guide can also be performed via the REST API. See the [Cluster API reference](https://docs.ellanetworks.com/reference/api/cluster/index.md) for details. # Scale Up a High Availability Cluster This guide walks through adding a node to an existing Ella Core high-availability cluster. For background on quorum, voter counts, and failover, see [High Availability](https://docs.ellanetworks.com/explanation/high_availability/index.md). To bring up the initial cluster, see [Deploy a High Availability Cluster](https://docs.ellanetworks.com/how_to/deploy_ha_cluster/index.md). ## Prerequisites - A running cluster deployed via [Deploy a High Availability Cluster](https://docs.ellanetworks.com/how_to/deploy_ha_cluster/index.md). - Admin credentials for the Ella Core UI, or an admin API token. - A prepared host meeting the [system requirements](https://docs.ellanetworks.com/reference/system_reqs/index.md), with Ella Core installed per the [Install](https://docs.ellanetworks.com/how_to/install/index.md) guide. Do **not** start the service yet. ## Add a node 1. On any existing node, open the Ella Core UI and navigate to the **Cluster** page. 1. Click **Add Node**, select the next free node ID (for example `4`), click **Mint Token**, and copy the token. 1. On the new host, create `core.yaml` using the same shape as the other nodes. List every node — including the new one — in `peers`, and paste the token into `join-token`: core.yaml (new node) ``` cluster: enabled: true node-id: 4 bind-address: "10.0.0.4:7000" peers: - "10.0.0.1:7000" - "10.0.0.2:7000" - "10.0.0.3:7000" - "10.0.0.4:7000" join-token: "ejYM..." ``` 1. Start Ella Core on the new host: ``` sudo snap start --enable ella-core.cored ``` 1. On the **Cluster** page, verify the new node appears and is shown as a **Voter** and **Healthy**. It joins the voting set directly. ## Verify the new cluster size On the **Cluster** page, confirm: - The expected number of voters is listed. - Exactly one node is **Leader**. - Every listed node is **Healthy**. - **Failure tolerance** matches the expected value (`1` for 3 voters, `2` for 5 voters). ## Keep peer configs in sync On every existing node, add the new node's `host:port` to `cluster.peers` in `core.yaml`. The change takes effect at the next restart; no immediate restart is required. Note All steps in this guide can also be performed via the REST API. See the [Cluster API reference](https://docs.ellanetworks.com/reference/api/cluster/index.md) for details. Any interface used by Ella Core can be on a VLAN interface. First, configure the VLAN on the host system, then use the name of the VLAN interface in the configuration file: ``` logging: system: level: "info" output: "stdout" audit: output: "stdout" db: path: "/var/snap/ella-core/common/data/ella.db" interfaces: n2: address: "10.3.0.2" ngap-port: 38412 n3: name: "ens5.103" n6: name: "ens3.106" api: address: "0.0.0.0" port: 5002 tls: cert: "/var/snap/ella-core/common/cert.pem" key: "/var/snap/ella-core/common/key.pem" datapath: attach-mode: "xdp-native" telemetry: enabled: false ``` # Reference # Reference This section provides detailed reference material for Ella Core, from configuration options to complete API documentation. - [API](https://docs.ellanetworks.com/reference/api/index.md) - [Configuration File](https://docs.ellanetworks.com/reference/config_file/index.md) - [Production Hardening](https://docs.ellanetworks.com/reference/production_hardening/index.md) - [Observability](https://docs.ellanetworks.com/reference/observability/index.md) - [Connectivity](https://docs.ellanetworks.com/reference/connectivity/index.md) - [Performance](https://docs.ellanetworks.com/reference/performance/index.md) - [System Requirements](https://docs.ellanetworks.com/reference/system_reqs/index.md) - [Supported Mobile Equipment](https://docs.ellanetworks.com/reference/supported_mobile_equipment/index.md) - [3GPP Compliance](https://docs.ellanetworks.com/reference/3gpp_compliance/index.md) # 3GPP Compliance Ella Core implements 3GPP-standard interfaces for 4G and 5G SA. Note Need a procedure or capability that is not supported for a production deployment? Open an [enhancement proposal](https://github.com/ellanetworks/core/issues/new?template=enhancement_proposal.yml) and tell us about your use case. ## Supported ### Interfaces | Interface | Transport | | --------- | ------------------------------ | | N1 | NAS, over N2 | | N2 | NGAP over SCTP | | N3 | GTP-U over UDP (IPv4 and IPv6) | | N6 | IP | | S1-MME | S1AP over SCTP | | S1-U | GTP-U over UDP (IPv4 and IPv6) | | SGi | IP | Ella Core is a single binary and does not expose internal 3GPP interfaces. See [Architecture](https://docs.ellanetworks.com/explanation/architecture/index.md). ### Registration and mobility - **Registration.** 4G: attach, detach, and normal and periodic tracking area update. 5G: initial, mobility, and periodic registration, and UE- and network-initiated deregistration. - **Service request.** An idle UE returns to connected mode to resume its session. - **Paging.** Ella Core pages an idle UE when downlink data arrives for it. - **Handover.** 4G: S1 handover, and X2 handover via the Path Switch procedure. 5G: Xn handover, and N2 handover between radios served by Ella Core. - **4G/5G interworking.** A device moving between 4G and 5G keeps its IP address and its session. ### Sessions Ella Core carries IP data sessions for 4G and 5G subscribers. - **Session management.** 4G PDN connectivity and 5G PDU sessions: establishment, modification, and release, including network-requested procedures. - **Session types.** IPv4, IPv6, and IPv4v6. - **QoS.** One non-GBR QoS flow per session. ### Security - **Procedures.** Identity, authentication, and security mode control. - **Authentication.** EPS-AKA on 4G, 5G-AKA on 5G. - **Subscriber identity concealment.** SUCI with the null scheme, Profile A, and Profile B, on 5G. - **Ciphering and integrity.** The null, SNOW 3G, and AES algorithms: EEA0/1/2 and EIA0/1/2 on 4G, NEA0/1/2 and NIA0/1/2 on 5G. ### Location Cell identity and E-CID positioning: LPPa on 4G, NRPPa on 5G. See the [Location API](https://docs.ellanetworks.com/reference/api/location/index.md), which is beta. ## Limitations - **No voice.** Ella Core provides no IMS, VoLTE, or VoNR. - **No emergency services.** Emergency sessions and emergency service requests are rejected. - **No roaming.** Ella Core is a self-contained core for a single network; there is no S6a, S8, or inter-operator interface. # Configuration File Ella is configured using a yaml formatted file. Start Ella core with the `--config` flag to specify the path to the configuration file. ## Parameters - `logging` (object): The logging configuration. - `system` (object): The system logging configuration. - `level` (string): The log level. Options are `debug`, `info`, `warn`, `error`, and `fatal`. - `output` (string): The output for the logs. Options are `stdout` and `file`. - `path` (string): The path to the log file. This is only used if the output is set to `file`. - `audit` (object): The audit logging configuration. - `output` (string): The output for the logs. Options are `stdout` and `file`. - `path` (string): The path to the log file. This is only used if the output is set to `file`. - `db` (object): The database configuration. - `path` (string): The path to the SQLite database file. The parent directory must already exist. The file should be named `ella.db`, as backup and restore expect that name. - `interfaces` (object): The network interfaces configuration. - `n2` (object): The configuration for the n2 interface (N2 in 5G, S1-MME in 4G). This is the control-plane interface to the radios; the same interface serves both 5G gNBs and 4G eNBs over SCTP. - `name` (string): The name of the network interface to listen on (optional: either name or address must be provided). When set, the server binds to all IP addresses configured on this interface. Link-local addresses (IPv6 link-local and IPv4 link-local) are automatically excluded. - `address` (string): The IP address to listen on. Supports both IPv4 and IPv6 addresses (optional: either name or address must be provided). When set, the server binds to this specific address. - `ngap-port` (int, optional): The SCTP port for the 5G N2 / NGAP listener. Default `38412`. - `s1ap-port` (int, optional): The SCTP port for the 4G S1-MME / S1AP listener. Default `36412`. - `port` (int, optional): Deprecated alias for `ngap-port`. Cannot be set together with `ngap-port`. - `n3` (object): The configuration for the n3 interface (N3 in 5G, S1-U in 4G). This interface should be connected to the radios. - `name` (string): The name of the network interface (optional: either name or address must be provided). - `address` (string): The address to listen on. Supports both IPv4 and IPv6 (optional: either name or address must be provided). - `n6` (object): The configuration for the n6 interface (N6 in 5G, SGi in 4G). This interface should be connected to the internet. - `name` (string): The name of the network interface. - `api` (object): The configuration for the api interface. - `name` (string): The name of the network interface to listen on (optional: either name or address must be provided). When set, the server listens on all addresses (`0.0.0.0`) but uses `SO_BINDTODEVICE` to restrict incoming traffic to this interface. Use this when you want to bind to a device without pinning to a specific IP address. - `address` (string): The IP address to listen on. Supports both IPv4 and IPv6 addresses (optional: either name or address must be provided). When set, the server binds to this specific address. - `port` (int): The port to listen on. - `tls` (object): The TLS configuration (optional). - `cert` (string): The path to the TLS certificate file (optional). - `key` (string): The path to the TLS key file (optional). - `datapath` (object, optional): The datapath configuration. When omitted, the datapath attaches at the XDP hook in driver mode where the network interface supports it, and at the TCX hook otherwise. - `attach-mode` (string, optional): The kernel hook the datapath attaches to: `xdp-native`, `tcx`, or `xdp-generic`. See [the eBPF attach mode explanation](https://docs.ellanetworks.com/explanation/user_plane_packet_processing_with_ebpf/index.md). - `xdp` (object, deprecated): Replaced by `datapath`. Cannot be set together with `datapath`. - `attach-mode` (string): `native` is equivalent to `datapath.attach-mode: xdp-native`, `generic` to `xdp-generic`. - `telemetry` (object): The telemetry configuration. - `enabled` (boolean): Whether telemetry is enabled or not. Default is `false`. - `otlp-endpoint` (string): The endpoint for the OpenTelemetry Protocol (OTLP) collector. - `cluster` (object): Clustering configuration for high-availability deployments. See [Clustering](#clustering) for the walkthrough. - `enabled` (boolean): Enables HA mode. When `false`, Ella Core runs as a standalone single-server instance. - `node-id` (int, 1–63): Unique per node. Baked into this node's self-signed cluster certificate (SPIFFE URI) and the GUTIs it issues. - `bind-address` (string): `host:port` the cluster listener binds to. Carries Raft consensus and cluster HTTP over mTLS. - `advertise-address` (string, optional): `host:port` peers use to reach this node. Host may be an IP or DNS name. Defaults to `bind-address`. Must appear in `peers` and must not use an unspecified IP. - `peers` (list of strings): `host:port` of every node in the cluster. Host may be an IP or DNS name. Must include this node's own `advertise-address` (or `bind-address` if `advertise-address` is unset) as the same string. - `join-token` (string, optional): Single-use token minted on an existing voter via `POST /api/v1/cluster/pki/join-tokens`. Required on the first boot of a node joining an existing cluster; consumed and ignored on subsequent starts. Its presence also tells the daemon that this node is a joiner, not the founder. - `initial-suffrage` (string, optional): `voter` or `nonvoter`. Defaults to `voter`. - `join-timeout` (duration string, optional): Maximum wait for cluster formation during discovery. - `propose-timeout` (duration string, optional): Maximum wait for a Raft commit before the API returns 503. - `snapshot-interval` (duration string, optional): Minimum interval between automatic Raft snapshots. - `snapshot-threshold` (int, optional): Minimum number of applied log entries between automatic snapshots. - `trailing-logs` (int, optional): Number of Raft log entries retained after a snapshot so a briefly-disconnected follower can catch up by log replay instead of receiving a full snapshot. Defaults to `10240`, which is correct for the vast majority of deployments. Lower it only if the Raft log is growing unboundedly under sustained write load; raise it only if followers repeatedly fall behind and trigger snapshot installs. Setting it too low on a cluster with a large database can put followers in a loop where they keep downloading snapshots and never converge. Note When you use the Ella Core snap, the configuration file is located at `/var/snap/ella-core/common/core.yaml`. After modifying the configuration file, restart Ella Core with `sudo snap restart ella-core.cored` for the changes to take effect. ## Example ``` logging: system: level: "info" output: "stdout" audit: output: "file" path: "/var/log/ella_system.log" db: path: "/var/lib/ella-core/ella.db" interfaces: n2: address: "22.22.22.2" n3: name: "ens5" n6: name: "ens3" api: address: "0.0.0.0" port: 5002 tls: cert: "/etc/ella/cert.pem" key: "/etc/ella/key.pem" datapath: attach-mode: "xdp-native" telemetry: enabled: true otlp-endpoint: "localhost:4317" ``` ## Clustering Enable clustering on each node to deploy Ella Core in a high-availability configuration. See [Deploy a High Availability Cluster](https://docs.ellanetworks.com/how_to/deploy_ha_cluster/index.md) for the walkthrough. ``` cluster: enabled: true node-id: 1 bind-address: "10.0.0.1:7000" peers: - "10.0.0.1:7000" - "10.0.0.2:7000" - "10.0.0.3:7000" ``` Note Write requests (POST, PUT, PATCH, DELETE) are automatically forwarded to the current Raft leader; reads are served by any node. ## IPv6 Support Ella Core supports IPv6 addresses for the management interface (`api`), the radio interface (`n2`) and the GTPU interface (`n3`). The following example demonstrates using an IPv6 address for those interfaces: ``` interfaces: n2: address: "2001:db8::1" n3: address: "2001:db8::1" n6: name: "ens3" api: address: "2001:db9::1" port: 5002 ``` The following example demonstrates using all non link-local addresses for those interfaces: ``` interfaces: n2: name: "ens5" n3: address: "ens4" n6: name: "ens3" api: name: "ens0" port: 5002 ``` ## GTP-U Transport over IPv6 Ella Core supports GTP-U tunnels over IPv6 for the N3 / S1-U interface (between the core and the radio). When a radio advertises a dual-stack transport address (both IPv4 and IPv6) in the N2 / S1-MME signaling and Ella Core is configured for dual-stack, Ella Core always prefers IPv6 for the GTP-U data path. To ensure Ella Core always uses IPv4 or IPv6 for GTP-U, specify an address of that family in the configuration file, or ensure only IPs of that family are configured on the interface. # Connectivity Ella Core uses 4 different interfaces by default: - **API**: The HTTP API and UI (HTTPS:5002) - **N2 / S1-MME**: The control plane interface between Ella Core and the Radio (4G: `SCTP:36412`, 5G: `SCTP:38412`) - **N3 / S1-U**: The user plane interface between Ella Core and the Radio (UDP:2152) - **N6 / SGi**: The user plane interface between Ella Core and the internet Connectivity in Ella Core # Combining interfaces It is possible to combine interfaces in the following manners. ## Combined N2 and N3 Many radios can use a single network link towards the core. In this case, N2 and N3 can be combined by using the same interface name for both of them in the configuration file. Combined N2 and N3 ## Combined API and N6 The API interface is often the management interface with internet access, and the N6 interface also requires internet access. They can be combined by using the same interface name for both in the configuration file. Combined API and N6 ## Combined API/N6 and combined N2/N3 It is possible to use both combination together to reduce the requirements to 2 interfaces. Combined All One or both of these interfaces can be virtual interfaces, with `veth`. See [Datapath constraints](#datapath-constraints). ## Combined on one interface Ella Core can also be run with a single network interface. It can be achieved by using the same interface name in the configuration file, or by using VLANs. # Using VLANs It is possible to use VLAN interfaces, with or without combining interfaces as described previously. In this case, the configuration file should contain the name of the VLAN interface, not the parent interface. # Datapath constraints The datapath attaches to N3 and N6 at the hook set by `datapath.attach-mode`. The shape of those interfaces constrains which modes work. - **veth, `xdp-native`**: an XDP program must be attached to the peer interface, see the [explanation](https://docs.ellanetworks.com/explanation/user_plane_packet_processing_with_ebpf/#xdp-redirect-on-veth-pairs) and the [setup guide](https://docs.ellanetworks.com/how_to/native_xdp_veth/index.md). - **veth, `xdp-generic`**: TX checksum offload must be disabled on both ends, see the [explanation](https://docs.ellanetworks.com/explanation/user_plane_packet_processing_with_ebpf/#checksum-offload-on-veth-pairs). - **Any interface, `tcx` or `xdp-generic`**: the interface must not deliver merged packets, see [Disable merged packets](https://docs.ellanetworks.com/how_to/disable_merged_packets/index.md). # NAT NAT is IPv4-only and applies to uplink traffic leaving N6, sourced from the N6 address. It is configured on the `Networking` page of the UI or through the [Networking API](https://docs.ellanetworks.com/reference/api/networking/index.md). - Source ports are allocated from 1024-32767. - Downlink traffic reaches a subscriber only when it matches a translation the subscriber's own traffic created. Anything else is dropped and counted in `app_upf_datapath_drop_total{reason="nat_unsolicited"}`. - Traffic NAT cannot translate is dropped and counted in the same metric under another `nat_` reason: IP fragments, and protocols without ports such as ESP, GRE and SCTP. Protocols that embed addresses in their payload, such as FTP in active mode, do not work. # IPv6 and dual-stack support Ella Core supports IPv6 and dual-stack on the following interfaces: - api - n2 - n3 They can be configured specifically with an IPv6 address to use IPv6. When specifying an interface, Ella Core will use all the non link-local addresses on the interface; if the interface is configured for dual-stack, Ella Core will use dual-stack on that interface. ## Dual-stack N3 / S1-U transport Both the N3 (5G) and S1-U (4G) interfaces support IPv4 and IPv6 transport for GTP-U tunnels. At startup Ella Core automatically resolves both IPv4 and IPv6 addresses from the configured interface and advertises them to the radio — to the gNB in the NGAP `TransportLayerAddress` (per 3GPP TS 38.414 Section 5.1), and to the eNB in the S1AP Transport Layer Address (per 3GPP TS 36.413) — each a 160-bit field carrying both families. The gNB selects its preferred address family in its response; for 4G, when the eNB offers both families, the IPv6 endpoint is used. Subsequent GTP-U encapsulation and decapsulation use the matching outer header type. IPv4-only and IPv6-only configurations are also supported. # Observability Ella Core supports four observability pillars: Metrics, Logs, Traces, and Profiles. ## 1. Metrics Ella Core exposes [Prometheus](https://prometheus.io/) metrics to monitor the health of an Ella Core instance. Please refer to the [metrics API documentation](https://docs.ellanetworks.com/reference/api/metrics/index.md) for more information on accessing metrics in Ella Core. ### Default Go metrics These metrics are used to monitor the health of the Go runtime and garbage collector. These metrics start with the `go_` prefix. ### Custom metrics These metrics are used to monitor the health of the system and the performance of the network. These metrics start with the `app_` prefix. The following custom metrics are exposed by Ella Core: | Metric | Description | Type | | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------- | | app_connected_radios | Number of radios currently connected to Ella Core, labeled by `rat`. | Gauge | | app_signaling_messages_total | Total radio signaling messages, labeled by `rat` (NGAP for 5G, S1AP for 4G), `direction` (inbound, outbound), and `type`. | Counter | | app_registered_subscribers | Number of subscribers currently registered in Ella Core, labeled by `rat` (5GS, EPS). | Gauge | | app_registration_attempts_total | Total UE registration (5G) and attach/tracking-area-update (4G) attempts, labeled by `rat`, `type`, and `result`. | Counter | | app_sessions_total | Number of active sessions currently in Ella Core, labeled by `rat`. | Gauge | | app_session_establishment_attempts_total | Total session establishment attempts, labeled by `rat` (5G PDU sessions, 4G EPS sessions) and `result`. | Counter | | app_ip_addresses_allocated_total | The total number of IP addresses currently allocated to subscribers. | Gauge | | app_ip_addresses_total | The total number of IP addresses available for subscribers. | Gauge | | app_upf_datapath_forward_total | Packets the data plane forwarded, with labels for direction (uplink, downlink) and the action it took (pass, tx, redirect). The action is the data plane's own decision, not the hook verdict, so it means the same thing in `xdp-native`, `xdp-generic` and `tcx`. | Counter | | app_upf_datapath_drop_total | Packets the data plane did not forward, with labels for direction (uplink, downlink) and reason. | Counter | | app_upf_datapath_fib_lookup_total | FIB lookup outcomes in the data plane, with labels for direction (uplink, downlink) and result matching kernel return codes (success, no_neigh, blackhole, unreachable, prohibit, no_src_addr, frag_needed, not_fwded, fwd_disabled, unsupp_lwt), plus error_ipv4 and error_ipv6 for a lookup the kernel rejected. | Counter | | app_uplink_bytes | The total number of bytes transmitted in the uplink direction (N3 -> N6). This value includes the Ethernet header. | Counter | | app_downlink_bytes | The total number of bytes transmitted in the downlink direction (N6 -> N3). This value includes the Ethernet header. | Counter | | app_api_requests_total | Total number of HTTP requests by method, endpoint, and status code | Counter | | app_api_request_duration_seconds | HTTP request duration histogram in seconds | Histogram | | app_api_authentication_attempts_total | Total number of authentication attempts by type and result | Counter | | app_database_storage_bytes | The total storage used by the database in bytes. This is the size of the database file on disk. | Gauge | | app_database_queries_total | Total number of database queries by table and operation | Counter | | app_database_query_duration_seconds | Duration of database queries | Histogram | | app_raft_changeset_bytes_total | SQLite changeset bytes applied through the Raft FSM. Emitted only when clustering is enabled. | Counter | Note When clustering is enabled, Ella Core also exports the full upstream [hashicorp/raft](https://github.com/hashicorp/raft) metrics suite (prefix `raft_`). These cover cluster state, leadership, replication, FSM apply latency, and snapshotting. The most useful ones for HA monitoring are: - `raft_state_leader`, `raft_state_follower`, `raft_state_candidate` — counters incremented on each state transition. Rate indicates leadership flapping. - `raft_leader_lastContact` — time since the leader last heard from a majority of peers (leader-only). Stale values indicate leader isolation. - `raft_peers` — number of servers in the cluster configuration. - `raft_fsm_apply` — FSM apply latency histogram. Covers the changeset apply path. - `raft_replication_appendEntries_rpc`, `raft_replication_heartbeat` — per-peer replication latency, labeled by `peer_id`. Slow or absent values indicate an unhealthy follower. - `raft_transition_heartbeat_timeout`, `raft_transition_leader_lease_timeout` — counters for failure-driven transitions. - `raft_oldestLogAge` — age of the oldest retained log entry. Growing unbounded indicates snapshot/compaction is stuck. - `raft_commitTime`, `raft_commitNumLogs` — commit latency and batch size on the leader. ## 2. Logs Ella Core produces three types of logs: - **System Logs**: General operational information about the system. - **Audit Logs**: Logs of user actions for security and compliance. You can view audit logs and manage their retention via the [API](https://docs.ellanetworks.com/reference/api/audit_logs/index.md) and the Web UI. - **Radio Logs**: Logs related to NGAP (5G) and S1AP (4G) messages. You can view radio logs and manage their retention via the [API](https://docs.ellanetworks.com/reference/api/radios/index.md) and the Web UI. All logs are output in **JSON format** with structured fields for easy parsing and ingestion into log aggregation systems like Loki, Elasticsearch, or Splunk. For more information on configuring logging in Ella Core, refer to the [Configuration File](https://docs.ellanetworks.com/reference/config_file/index.md) documentation. Note Ella Core does not assist with log rotation; we recommend using a log rotation tool to manage log files. ## 3. Traces Ella Core supports distributed tracing using [OpenTelemetry](https://opentelemetry.io/). Traces are exported via [OTLP (gRPC)](https://opentelemetry.io/docs/specs/otlp/) to any compatible backend such as Jaeger, Tempo, or Honeycomb. Traces are collected for the following components: - **NGAP/S1AP**: Traces for NGAP (5G) and S1AP (4G) message handling between radios and Ella Core. - **API**: Traces for HTTP requests to the REST API. For more information on configuring tracing in Ella Core, refer to the [Configuration File](https://docs.ellanetworks.com/reference/config_file/index.md) documentation. ## 4. Profiles Ella Core exposes the [http/pprof](https://pkg.go.dev/net/http/pprof) API for CPU and memory profiling analysis. This allows users to collect and analyze profiles of Ella Core using visualization tools like [pprof](https://pkg.go.dev/net/http/pprof) or [pyroscope](https://grafana.com/oss/pyroscope/). For more information on accessing the pprof API in Ella Core, refer to the [pprof API documentation](https://docs.ellanetworks.com/reference/api/pprof/index.md). ## Alert Rules Ella Core ships with pre-configured [Grafana alert rules](https://github.com/ellanetworks/core/tree/main/observability/grafana/alerting/alerts.yml) that detect the most important failure scenarios. ### Network Health | Alert | Severity | Condition | | ------------------------------- | -------- | --------------------------------------------------------------------- | | No Radios Connected | Critical | No radios connected for 2 minutes | | High Registration Failure Rate | Critical | More than 10% of registration/attach attempts rejected over 5 minutes | | High PDU Session Failure Rate | Critical | More than 10% of session establishments rejected over 5 minutes | | IP Address Pool Near Exhaustion | Warning | More than 90% of the data network IP pool is allocated | ### Data Plane Health | Alert | Severity | Condition | | -------------------------------- | -------- | ----------------------------------------------------------------- | | High Data Plane Packet Drop Rate | Warning | More than 10 packets/s dropped by the data plane for 5 minutes | | No Data Plane Traffic | Critical | Radios connected but zero throughput for 10 minutes | | Data Plane Aborted Actions | Critical | Any aborted actions for 2 minutes (indicates eBPF program errors) | ### API Health | Alert | Severity | Condition | | ---------------------------- | -------- | ---------------------------------------------------------------- | | High API Error Rate | Warning | More than 5% of API responses are 5xx errors over 5 minutes | | High API Latency | Warning | P99 API response time exceeds 2 seconds over 5 minutes | | Authentication Failure Spike | Warning | More than 25% of API authentication attempts fail over 5 minutes | ### Infrastructure Health | Alert | Severity | Condition | | --------------------------- | -------- | ------------------------------------------------------- | | Instance Down | Critical | Ella Core instance is unreachable | | High Memory Usage | Warning | Process memory exceeds 1 GiB for 5 minutes | | High Goroutine Count | Warning | More than 10,000 goroutines for 5 minutes | | High Database Query Latency | Warning | P99 database query latency exceeds 500ms over 5 minutes | | Large Database Size | Warning | Database file exceeds 1 GiB for 10 minutes | ## Dashboards Ella Core ships with [Grafana](https://grafana.com/) dashboards that you can import using the Dashboard IDs provided below. ### Network Health This dashboard uses Prometheus metrics to provide real-time visibility into all aspects of your mobile private network deployment, from radio connectivity and subscriber sessions to system performance and data plane throughput. Grafana dashboard for Network Health. - Data Sources: Prometheus - Dashboard ID: 24751 - View online: [grafana.com/grafana/dashboards/24751/](https://grafana.com/grafana/dashboards/24751/) ### Deep Dive (for developers) This dashboard uses metrics, logs, traces, and profiles to provide deep insights into the internal workings of Ella Core. It is intended for developers and advanced users who want to understand the performance and behavior of Ella Core at a granular level. We recommend running Grafana Alloy to collect all signals ([example configuration file](https://github.com/ellanetworks/core/tree/main/observability/alloy)). A complete [example observability stack](https://github.com/ellanetworks/core/tree/main/observability) (Grafana, Mimir, Loki, Tempo, Pyroscope) is provided as a Docker Compose setup. Grafana dashboard for Deep Dive. - Data Sources: Mimir, Loki, Tempo, Pyroscope - Dashboard ID: 24770 - View online: [grafana.com/grafana/dashboards/24770/](https://grafana.com/grafana/dashboards/24770/) # Performance This reference document contains performance test results of Ella Core, covering data plane throughput and latency as well as session support. ## Results ### Throughput (iPerf3) The following table outlines the performance test results of Ella Core's data plane throughput: | Uplink (Gbps) | Downlink (Gbps) | | ------------- | --------------- | | 10+ | 10+ | The tests could saturate the 10Gbps connection consistently, with or without NAT enabled, with CPU usage peaking at 8%. ### Throughput (TRex) The following table outlines the performance test results of Ella Core's data plane throughput: | Packet size (bytes) | Uplink | Downlink | | ------------------- | --------- | --------- | | 46 | 2.50 Mpps | 5.20 Mpps | | 494 | 2.17 Mpps | 2.17 Mpps | | 1456 | 812 Kpps | 812 Kpps | The packet size represents only the IP packet for the subscribers and ignores Ethernet and GTP encapsulation. Downlink performance is better as the number of packets increases as the flows are able to handled by different cores using [Receive Side Scaling (RSS)](https://www.kernel.org/doc/html/latest/networking/scaling.html). The uplink flows are all seen by the NIC drivers as the same flow, because of the GTP encapsulation. When enabling NAT, we got the following results: | Packet size (bytes) | Uplink | | ------------------- | --------- | | 46 | 1.72 Mpps | | 494 | 1.70 Mpps | | 1456 | 812 Kpps | Downlink with NAT was not supported by our testing script. ### Latency (Round-trip) The following table outlines the performance test results of Ella Core's data plane latency: | Average (ms) | Best (ms) | Worst (ms) | Mean Deviation (ms) | | ------------ | --------- | ---------- | ------------------- | | 1.160 | 0.803 | 1.457 | 0.194 | The value represents the round-trip-response times from the subscriber's device to the server and back. ### Session Support Ella Core can support up to **1000 subscribers** using a data session simultaneously. This was tested with **ueransim**, using 10 simulated gNodeBs each handling 100 subscribers. ## Methodology We performed performance tests with Ella Core running on a baremetal system with the following specifications: - **OS**: Ubuntu 24.04 LTS - **CPU**: 12th Gen Intel(R) Core(TM) i5-1540p - **RAM**: 32GB - **Disk**: 512GB NVMe SSD - **NICs**: 2 x Intel Corporation 82599ES 10-Gigabit The RAN simulator used was [Packet Rusher](https://github.com/HewlettPackard/PacketRusher) Performance Testing Environment ### iPerf3 Throughput testing We performed the throughput tests using [iPerf3](https://iperf.fr/). Test parameters: - **Version**: v3.16 - **Protocol**: TCP - **Duration**: 120 seconds - **Streams**: 4 - **MSS**: 1416 bytes - **Runs (average over)**: 5 ### TRex Throughput testing We performed those tests using [TRex](https://trex-tgn.cisco.com). Test parameters: - **Version**: v3.08 - **Duration**: 120 seconds - **Drop rate**: 0% - **Streams**: 64 ### Latency testing We performed latency tests using ping. Test parameters: - **Count**: 30 # Production Hardening This reference document provides guidelines for operating Ella Core in a production environment. ## Recommendations - **Deploy on a production-grade system**. Ensure your system meets the [production requirements](https://docs.ellanetworks.com/reference/system_reqs/index.md). - **Deploy with the snap**: Use the [Snap installation method](https://docs.ellanetworks.com/how_to/install/#__tabbed_1_1) to deploy Ella Core. - **Isolate network interfaces**: Use separate network interfaces for N2, N3, N6, and API traffic. - **Use TLS**: Configure TLS for the API interface in the configuration file. Use certificates from a trusted Certificate Authority (CA). - **Use the fastest attach mode the interfaces allow**: set `datapath.attach-mode` to `xdp-native` where the driver supports it. On veth interfaces it needs an XDP program on the peer, and `tcx` is the practical choice — see [Datapath constraints](https://docs.ellanetworks.com/reference/connectivity/#datapath-constraints). - **Set logging level to info**: Configure system logging level to `info` and use file output. - **Disable telemetry**: Disable telemetry in the configuration file. - **Rotate logs**: Implement log rotation for system and audit logs. - **Back up the database**: Back up the database file on a **daily** basis. Retain backups for at least **7 days**. Backup archives contain sensitive secrets; store and transfer them encrypted and treat them as admin credentials. - **Monitor metrics**: Operate an external Observability stack to collect and visualize metrics exposed by Ella Core. # Supported Mobile Equipment Ella Core's control plane and user plane interfaces follow 3GPP standards. This means that any radio and user equipment (UE) that follows the same standards should be compatible. This lists contains radios and UEs that were explicitely validated against Ella Core. > Some integrations were validated by third-party vendors, partners, or the open-source community. ## Radios ### Commercial Radios - **[CableFree 5G Small Cell](https://www.cablefree.net/5g-lte/5g-small-cell-base-station-radios/)** - **[Baicells Stellar 227](https://www.baicells.com/stellar227)** - **[Baicells Neutrino 220](https://www.baicells.com/neutrino220)** - **[Vankom VKScell-g3](https://www.vankom.com/nps-scell-en/)** ### Open RAN Radios & SDR Platforms - **[Ettus Research USRP](https://www.ettus.com/all-products/)** - **[OpenAirInterface 5G RAN](https://openairinterface.org/)** - **[OCUDU](https://ocudu.org/)** (previously srsRAN 5G) - **[srsENB](https://www.srslte.com/)** ### Simulators - **[Aether GNBSIM](https://docs.aetherproject.org/master/onramp/gnbsim.html)** - **[UERANSIM](https://github.com/aligungr/UERANSIM)** ## User Equipment ### iOS Devices - **iPhone 16** ### Android Devices - **Samsung Galaxy A56 5G** - **Motorola G73 5G** - **Motorola Moto G 5G (2024)** - **Huawei P40 5G** - **OnePlus Nord 2 CE 5G** - **Crosscall CORE-Z5** ### 5G Modules - **Quectel RM520N-GL** - **Quectel RedCap RG255C-GL** ### Simulators - **[Aether GNBSIM](https://docs.aetherproject.org/master/onramp/gnbsim.html)** - **[UERANSIM](https://github.com/aligungr/UERANSIM)** # System Requirements Ella Core runs on major Linux distributions including **Ubuntu**, **Ubuntu Core**, **Debian**, and **Arch Linux** (kernel 6.8 or later). It supports **amd64** and **arm64** architectures. | | Minimum | Production | | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **CPU** | 1 core | 4 cores | | **Memory** | 1 GB RAM | 8 GB RAM | | **Storage** | 10 GB disk space | 50 GB disk space | | **Network** | 1 interface with XDP support and appropriate MTU (see the official [XDP documentation](https://docs.ebpf.io/linux/program-type/BPF_PROG_TYPE_XDP/) for driver support and the [connectivity reference](https://docs.ellanetworks.com/reference/connectivity/index.md)) | 4 interfaces with XDP support and appropriate MTU (see the official [XDP documentation](https://docs.ebpf.io/linux/program-type/BPF_PROG_TYPE_XDP/) for driver support and the [connectivity reference](https://docs.ellanetworks.com/reference/connectivity/index.md)) | | **Operating System** | — | Ubuntu 26.04 LTS | # API Ella Core exposes a RESTful API for managing subscribers, radios, data networks, profiles, slices, policies, users, routes, operator configuration, and subscriber location (beta). ## Authentication Almost every operation requires a client token. The client token must be sent as Authorization HTTP Header using the Bearer scheme. That token can either be the JWT returned by the [login](https://docs.ellanetworks.com/reference/api/auth/#login) endpoint or an [API token](https://docs.ellanetworks.com/reference/api/users/#create-an-api-token). ## Responses Ella Core's API responses are JSON objects with the following structure: ``` { "result": "Result content", "error": "Error message", } ``` Info GET calls to the `/metrics` endpoint don't follow this rule, it returns text response in the [Prometheus exposition format](https://prometheus.io/docs/instrumenting/exposition_formats/#text-format-details). ## OpenAPI Specification The full API is described by an [OpenAPI 3.1](https://spec.openapis.org/oas/v3.1.0) specification embedded in the binary. Fetch it from any running Ella Core instance: ``` GET /api/v1/openapi.yaml ``` This endpoint is unauthenticated. The spec can be used to generate client libraries, import into tools like Postman or Swagger UI, or integrate with AI agents and automation frameworks that consume OpenAPI definitions. ## Status codes - 200 - Success. - 201 - Created. - 400 - Bad request. - 401 - Unauthorized. - 429 - Too many requests. - 500 - Internal server error. ## Client Ella Core provides a [Go client](https://pkg.go.dev/github.com/ellanetworks/core/client) for interacting with the API. ``` package main import ( "log" "github.com/ellanetworks/core/client" ) func main() { clientConfig := &client.Config{ BaseURL: "http://127.0.0.1:5002", APIToken: "ellacore_Xl2yU1rcy2BP_8q5iOpNBtoXLYdwddbBCHInx", } ella, err := client.New(clientConfig) if err != nil { log.Println("Failed to create client:", err) } createSubscriberOpts := &client.CreateSubscriberOptions{ Imsi: "001010100000033", Key: "5122250214c33e723a5dd523fc145fc0", SequenceNumber: "000000000022", ProfileName: "default", } err = ella.CreateSubscriber(createSubscriberOpts) if err != nil { log.Println("Failed to create subscriber:", err) } } ``` # Audit Logs In addition to system logs output, Ella Core exposes audit logs through its API. These logs are useful in the day-to-day operation of the network. ## List Audit Logs This path returns the list of audit logs. | Method | Path | | ------ | -------------------- | | GET | `/api/v1/logs/audit` | ### Query Parameters | Name | In | Type | Default | Allowed | Description | | ---------- | ----- | ------ | ------- | ------- | ------------------------------------------------------------------------------------------------------------------------ | | `page` | query | int | `1` | `>= 1` | 1-based page index. | | `per_page` | query | int | `25` | `1…100` | Number of items per page. | | `user` | query | string | — | — | Filter audit logs by user email. When omitted, all audit logs are returned. | | `action` | query | string | — | — | Filter audit logs by action identifier (e.g. `create_subscriber`, `auth_login`). When omitted, all actions are returned. | | `start` | query | string | — | — | Start date (`YYYY-MM-DD`). Only logs on or after this date are returned. | | `end` | query | string | — | — | End date (`YYYY-MM-DD`). Only logs before (exclusive) the day after are returned. | ### Sample Response ``` { "result": { "items": [ { "id": "1", "timestamp": "2025-08-12T16:58:00.810-0400", "level": "INFO", "user": "guillaume@ellanetworks.com", "action": "create_user", "ip": "127.0.0.1", "details": "User created user: newuser@ellanetworks.com with role: 1" } ], "page": 1, "per_page": 10, "total_count": 1 } } ``` ## Update Audit Log Retention Policy This path update the audit log retention policy. | Method | Path | | ------ | ------------------------------ | | PUT | `/api/v1/logs/audit/retention` | ### Parameters - `days` (integer): The number of days to retain audit logs. Must be a positive integer. ### Sample Response ``` { "result": { "message": "Audit log retention policy updated successfully" } } ``` ## Get Audit Log Retention Policy This path returns the current audit log retention policy. | Method | Path | | ------ | ------------------------------ | | GET | `/api/v1/logs/audit/retention` | ### Parameters None ### Sample Response ``` { "result": { "days": 7 } } ``` # Authentication This section describes the RESTful API for system user authentication. ## Login This path logs the user in and sets an httpOnly session cookie valid for 30 days. | Method | Path | | ------ | -------------------- | | POST | `/api/v1/auth/login` | ### Parameters - `email` (string): The email to authenticate with. - `password` (string): The password to authenticate with. ### Sample Response ``` { "result": { "message": "Login successful" "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiZW1haWwiOiJhZG1pbkBlbGxhbmV0d29ya3MuY29tIiwicm9sZV9pZCI6MSwiZXhwIjoxNzcwNjgwNTA2fQ.1w9fxtLIfwY4sBOCwdXIZtSmDk8YaVEseAQHJ-5rUXI" } } ``` ## Refresh This path validates the current session cookie and returns a new JWT token. This token can then be used to authenticate future requests by sending it in the `Authorization` header using the `Bearer ` scheme. This token is valid for 15 minutes. | Method | Path | | ------ | ---------------------- | | POST | `/api/v1/auth/refresh` | Warning Avoid relying on refresh tokens for API access since they require regular renewals. Instead, use [API tokens](https://docs.ellanetworks.com/reference/api/users/#create-an-api-token) which offer explicit expiry settings and can be manually revoked. ### Parameters None ### Sample Response ``` { "result": { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsImV4cCI6MTczNTU4NTk0MX0.0BsZVMLCzJ6mzCXlf3qfAR2k6Fk7aUsGfHV7Tj1Dqy4" } } ``` ## Lookup a JWT Token This path returns whether a JWT token is valid. The token must be sent in the `Authorization` header, like other authenticated requests. | Method | Path | | ------ | --------------------------- | | POST | `/api/v1/auth/lookup-token` | ### Parameters None ### Sample Response ``` { "result": { "valid": true, } } ``` ## Rotate Secret Generates a new JWT signing secret. All existing user sessions are immediately invalidated — users must re-authenticate. API tokens (prefixed `ellacore_`) are not affected. Requires admin role. | Method | Path | | ------ | ---------------------------- | | POST | `/api/v1/auth/rotate-secret` | ### Parameters None ### Sample Response ``` { "result": { "message": "Secret rotated successfully. All user sessions have been invalidated." } } ``` # Backup This path creates a backup of the Ella Core database. The backup archive contains sensitive secrets. Store and transfer it encrypted, and treat it as you would an admin credential. ## Create a Backup | Method | Path | | ------ | ---------------- | | POST | `/api/v1/backup` | ### Parameters None ### Sample Response The response contains the backup file as a downloadable attachment. # Cluster This section describes the RESTful API for managing cluster membership. These endpoints are only available when clustering is enabled in the configuration file. ## List Cluster Members This path returns the list of cluster members. | Method | Path | | ------ | ------------------------- | | GET | `/api/v1/cluster/members` | ### Parameters None ### Sample Response ``` { "result": [ { "nodeId": 1, "raftAddress": "10.0.0.1:7000", "apiAddress": "https://10.0.0.1:5000", "binaryVersion": "v1.15.0", "suffrage": "voter", "isLeader": true, "drainState": "active" }, { "nodeId": 2, "raftAddress": "10.0.0.2:7000", "apiAddress": "https://10.0.0.2:5000", "binaryVersion": "v1.15.0", "suffrage": "voter", "isLeader": false, "drainState": "active" } ] } ``` ## Remove a Cluster Member This path removes a node from the Raft cluster. The node must be drained first (`drainState == "drained"`) unless `force=true` is set. The current leader cannot be removed regardless of `force`. Requires admin privileges. | Method | Path | | ------ | ------------------------------ | | DELETE | `/api/v1/cluster/members/{id}` | ### Query Parameters | Name | In | Type | Default | Description | | ------- | ----- | ---- | ------- | ------------------------------ | | `force` | query | bool | `false` | Bypass the drain precondition. | ### Sample Response ``` { "result": { "message": "Cluster member removed" } } ``` ## Promote a Cluster Member This path promotes a nonvoter node to a voter in the Raft cluster. Autopilot promotes healthy nonvoters automatically; use this endpoint to promote immediately. Requires admin privileges. | Method | Path | | ------ | -------------------------------------- | | POST | `/api/v1/cluster/members/{id}/promote` | ### Parameters None ### Sample Response ``` { "result": { "message": "Cluster member promoted to voter" } } ``` ## Get Autopilot State This path returns the live autopilot view of the cluster: per-peer health, voter roster, and failure tolerance. Only the leader can produce this state; followers proxy the request to the leader automatically. Requires admin privileges. | Method | Path | | ------ | --------------------------- | | GET | `/api/v1/cluster/autopilot` | ### Parameters None ### Sample Response ``` { "result": { "healthy": true, "failureTolerance": 1, "leaderNodeId": 1, "voters": [1, 2, 3], "servers": [ { "nodeId": 1, "raftAddress": "10.0.0.1:7000", "nodeStatus": "alive", "healthy": true, "isLeader": true, "hasVotingRights": true, "stableSince": "2026-04-20T08:15:02Z" }, { "nodeId": 2, "raftAddress": "10.0.0.2:7000", "nodeStatus": "alive", "healthy": true, "isLeader": false, "hasVotingRights": true, "stableSince": "2026-04-20T08:15:02Z" } ] } } ``` ## Drain Cluster Member This path drains a node and persists `drainState=drained`. The server runs the local drain side-effects on the target: signals connected RANs that this AMF's GUAMI is unavailable, stops the local BGP speaker, and transfers Raft leadership when the target is the current leader. A node must be drained before it can be removed. Requires admin privileges. | Method | Path | | ------ | ------------------------------------ | | POST | `/api/v1/cluster/members/{id}/drain` | ### Parameters None. ### Sample Response ``` { "result": { "drainState": "drained" } } ``` ## Resume Cluster Member This path reverses drain on a node: restarts the local BGP speaker (if BGP is enabled) and clears `drainState` back to `active`. RAN unavailability and transferred leadership are not reversed. Idempotent. Requires admin privileges. | Method | Path | | ------ | ------------------------------------- | | POST | `/api/v1/cluster/members/{id}/resume` | ### Parameters None ### Sample Response ``` { "result": { "message": "Cluster member resumed" } } ``` ## Mint Join Token This path mints a single-use HMAC token authorising `nodeID` to register its self-signed cluster certificate with the leader. The leader's own pinned certificate fingerprint is embedded in the token, so the joining node pins the bootstrap TLS handshake directly to the leader's certificate. Requires admin privileges. | Method | Path | | ------ | --------------------------------- | | POST | `/api/v1/cluster/pki/join-tokens` | ### Parameters - `nodeID` (integer): Node ID of the joining host. - `ttlSeconds` (integer, optional): Token lifetime in seconds. Defaults to `1800`. ### Sample Response ``` { "result": { "token": "AQAAAPx...", "expiresAt": 1714233600 } } ``` # Flow Reports Flow reports provide visibility into network traffic patterns and usage statistics for subscribers. These reports are stored in the database and can be queried with various filters and pagination options. ## Get Flow Reports This path returns a paginated list of flow reports with optional filtering. | Method | Path | | ------ | ---------------------- | | GET | `/api/v1/flow-reports` | ### Query Parameters | Name | In | Type | Default | Allowed | Description | | --------------- | ----- | ------ | -------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | | `page` | query | int | `1` | `>= 1` | 1-based page index. | | `per_page` | query | int | `25` | `1…100` | Number of items per page. | | `subscriber_id` | query | string | \`\` | | Filter by subscriber ID. | | `protocol` | query | int | \`\` | `1…255` | Filter by protocol number. | | `source` | query | string | \`\` | | Filter by source. Accepts: IP, IP:port, or :port. | | `destination` | query | string | \`\` | | Filter by destination. Accepts: IP, IP:port, or :port. | | `start` | query | string | `now-7d` | | Start date for flow reports. Format: YYYY-MM-DD. | | `end` | query | string | `now` | | End date for flow reports. Format: YYYY-MM-DD. | | `direction` | query | string | \`\` | `uplink`, `downlink` | Filter by traffic direction. | | `action` | query | string | \`\` | `allow`, `drop` | Filter by flow action. `allow` returns only accepted flows, `drop` returns only dropped flows. When omitted, all flows are returned. | | `group_by` | query | string | \`\` | `day`, `subscriber` | Grouping method for flow reports. When set, returns aggregated data instead of paginated list. | ### Sample Response (default, no `group_by`) ``` { "result": { "items": [ { "id": 1, "subscriber_id": "001019756139935", "source_ip": "192.168.1.100", "destination_ip": "8.8.8.8", "source_port": 10000, "destination_port": 53, "protocol": 17, "packets": 100, "bytes": 5000, "start_time": "2025-02-22T10:30:00.000Z", "end_time": "2025-02-22T10:30:01.000Z", "direction": "uplink", "action": "allow" } ], "page": 1, "per_page": 25, "total_count": 1 } } ``` ### Sample Response (`group_by=day`) ``` { "result": [ { "2025-02-22": [ { "id": 1, "subscriber_id": "001019756139935", "source_ip": "192.168.1.100", "destination_ip": "8.8.8.8", "source_port": 10000, "destination_port": 53, "protocol": 17, "packets": 100, "bytes": 5000, "start_time": "2025-02-22T10:30:00.000Z", "end_time": "2025-02-22T10:30:01.000Z", "direction": "uplink", "action": "allow" } ] }, { "2025-02-23": [ { "id": 2, "subscriber_id": "001019756139935", "source_ip": "192.168.1.100", "destination_ip": "1.1.1.1", "source_port": 20000, "destination_port": 443, "protocol": 6, "packets": 800, "bytes": 40000, "start_time": "2025-02-23T08:00:00.000Z", "end_time": "2025-02-23T08:05:00.000Z", "direction": "downlink", "action": "allow" } ] } ] } ``` ### Sample Response (`group_by=subscriber`) ``` { "result": [ { "001019756139935": [ { "id": 1, "subscriber_id": "001019756139935", "source_ip": "192.168.1.100", "destination_ip": "8.8.8.8", "source_port": 10000, "destination_port": 53, "protocol": 17, "packets": 100, "bytes": 5000, "start_time": "2025-02-22T10:30:00.000Z", "end_time": "2025-02-22T10:30:01.000Z", "direction": "uplink", "action": "allow" } ] }, { "001019756139936": [ { "id": 3, "subscriber_id": "001019756139936", "source_ip": "192.168.1.101", "destination_ip": "1.1.1.1", "source_port": 30000, "destination_port": 443, "protocol": 6, "packets": 500, "bytes": 25000, "start_time": "2025-02-22T12:00:00.000Z", "end_time": "2025-02-22T12:01:00.000Z", "direction": "downlink", "action": "allow" } ] } ] } ``` ## Get Flow Report Stats This path returns aggregated statistics for flow reports, including protocol breakdown and top uplink destinations. | Method | Path | | ------ | ---------------------------- | | GET | `/api/v1/flow-reports/stats` | ### Query Parameters The same filter parameters as [Get Flow Reports](#get-flow-reports) are supported (`subscriber_id`, `protocol`, `source`, `destination`, `direction`, `action`, `start`, `end`), excluding `page`, `per_page`, and `group_by`. ### Sample Response ``` { "result": { "protocols": [ { "protocol": 6, "count": 150 }, { "protocol": 17, "count": 75 } ], "top_destinations_uplink": [ { "ip": "8.8.8.8", "count": 50 }, { "ip": "1.1.1.1", "count": 30 } ] } } ``` ## Clear Flow Reports This path deletes all flow reports from the database. | Method | Path | | ------ | ---------------------- | | DELETE | `/api/v1/flow-reports` | ### Sample Response ``` { "result": { "message": "All flow reports cleared successfully" } } ``` ## Get Flow Reports Retention Policy This path returns the current flow reports retention policy. | Method | Path | | ------ | -------------------------------- | | GET | `/api/v1/flow-reports/retention` | ### Sample Response ``` { "result": { "days": 7 } } ``` ## Update Flow Reports Retention Policy This path updates the flow reports retention policy. | Method | Path | | ------ | -------------------------------- | | PUT | `/api/v1/flow-reports/retention` | ### Parameters - `days` (integer): The number of days to retain flow reports. Must be a positive integer. ### Sample Response ``` { "result": { "message": "Flow reports retention policy updated successfully" } } ``` # Initialize This section describes the RESTful API for initializing Ella Core. Initialization consists of creating the first admin user. This user can then create other users and manage the system. ## Initialize the System This path initializes the system by creating the first admin user. This endpoint can only be called if no users exist in the system. | Method | Path | | ------ | -------------- | | POST | `/api/v1/init` | ### Parameters - `email` (string): The email of the user. - `password` (string): The password of the user. ### Sample Response ``` { "result": { "message": "System initialized successfully" "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiZW1haWwiOiJhZG1pbkBlbGxhbmV0d29ya3MuY29tIiwicm9sZV9pZCI6MSwiZXhwIjoxNzcwNjgwNTU2fQ.fOdYkaC0ESZJza7THRx2N-llkij-BBhOAUP6IiPqcuQ" } } ``` # Location (beta) Ella Core supports two positioning methods: Cell ID and Enhanced Cell ID (E-CID). The Location API requests a subscriber's location, tracks deferred positioning sessions, and provisions the cell positions those estimates are drawn from. Beta The Location API is experimental and served under `/api/beta`. Its paths and payloads may change without notice. ## Locate a Subscriber This path requests a subscriber's current location. `immediate` returns an estimate directly; `periodic` and `triggered` open a positioning session; `cancel` terminates one. | Method | Path | | ------ | -------------------- | | POST | `/api/beta/location` | ### Query Parameters | Name | In | Type | Default | Allowed | Description | | --------- | ----- | ---- | ------- | -------------- | ---------------------------------------------------------------------- | | `verbose` | query | bool | `false` | `true`,`false` | Attach the `supplementaryMeasurements` block (raw NRPPa measurements). | ### Parameters - `request_type` (string): `immediate`, `periodic`, `triggered`, or `cancel`. - `supi` (string): Subscriber identity. Required unless `request_type` is `cancel`. - `method` (string, optional): `cell_id` or `ecid`. Defaults to `cell_id`. - `session_id` (string): Session to terminate. Required when `request_type` is `cancel`. - `qos_response_time_ms` (integer, optional): Requested response-time budget, in milliseconds. - `qos_horizontal_accuracy_m` (integer, optional): Requested horizontal accuracy, in metres. ### Responses | Status | Condition | | ------ | ------------------------------------------------------- | | `200` | `immediate` request; returns a location estimate. | | `201` | `periodic`/`triggered` request; returns a session `id`. | | `204` | `cancel` request; no body. | ### Sample Response (`immediate`) ``` { "result": { "locationEstimate": { "shape": "POINT_UNCERTAINTY_CIRCLE", "point": { "lat": 37.7749, "lon": -122.4194 }, "uncertainty": 150 }, "accuracyFulfilmentIndicator": "REQUESTED_ACCURACY_FULFILLED", "positioningDataList": [ { "method": "CELLID", "mode": "CONVENTIONAL", "usage": "SUCCESS_RESULTS_USED" } ], "ncgi": { "plmnId": { "mcc": "001", "mnc": "01" }, "nrCellId": "000000010" } } } ``` For a 4G (E-UTRA) subscriber the serving cell is reported as `ecgi` with a 7-hex-digit `eutraCellId`, and E-CID uses the `ECID` positioning method: ``` { "result": { "locationEstimate": { "shape": "POINT_UNCERTAINTY_CIRCLE", "point": { "lat": 37.7749, "lon": -122.4194 }, "uncertainty": 150 }, "accuracyFulfilmentIndicator": "REQUESTED_ACCURACY_FULFILLED", "positioningDataList": [ { "method": "ECID", "mode": "CONVENTIONAL", "usage": "SUCCESS_RESULTS_USED" } ], "ecgi": { "plmnId": { "mcc": "001", "mnc": "01" }, "eutraCellId": "0000001" } } } ``` ### Sample Response (`periodic`/`triggered`) ``` { "result": { "id": "018f9f3a-4c2e-7bd1-9f21-0a1b2c3d4e5f" } } ``` ## List Positioning Sessions This path returns the positioning sessions for a subscriber. | Method | Path | | ------ | -------------------------------- | | GET | `/api/beta/positioning/sessions` | ### Query Parameters | Name | In | Type | Default | Allowed | Description | | ------ | ----- | ---- | ------- | ------- | ------------------------------ | | `supi` | query | str | | | Subscriber identity. Required. | ### Response Fields | Field | Type | Description | | -------------- | ------- | ----------------------------------------------------- | | `id` | string | Session identifier. | | `supi` | string | Subscriber identity. | | `session_type` | integer | `0` immediate, `1` periodic, `2` triggered. | | `method` | string | Positioning method. | | `status` | integer | `0` active, `1` completed, `2` failed, `3` cancelled. | | `created_at` | integer | Creation time, Unix seconds. | | `updated_at` | integer | Last update time, Unix seconds. | ### Sample Response ``` { "result": [ { "id": "018f9f3a-4c2e-7bd1-9f21-0a1b2c3d4e5f", "supi": "imsi-001010000000001", "session_type": 1, "method": "ecid", "status": 0, "created_at": 1720000000, "updated_at": 1720000000 } ] } ``` ## Get a Positioning Session This path returns a positioning session, including its most recent location estimate. | Method | Path | | ------ | ------------------------------------- | | GET | `/api/beta/positioning/sessions/{id}` | ### Path Parameters | Name | Type | Description | | ---- | ------ | ------------------- | | `id` | string | Session identifier. | ### Response Fields | Field | Type | Description | | --------------------------- | ------- | ----------------------------------------------------- | | `id` | string | Session identifier. | | `supi` | string | Subscriber identity. | | `session_type` | integer | `0` immediate, `1` periodic, `2` triggered. | | `method` | string | Positioning method. | | `status` | integer | `0` active, `1` completed, `2` failed, `3` cancelled. | | `qos_response_time_ms` | integer | Requested response-time budget, in milliseconds. | | `qos_horizontal_accuracy_m` | integer | Requested horizontal accuracy, in metres. | | `last_result` | object | Most recent location estimate. | | `created_at` | integer | Creation time, Unix seconds. | | `updated_at` | integer | Last update time, Unix seconds. | ### Sample Response ``` { "result": { "id": "018f9f3a-4c2e-7bd1-9f21-0a1b2c3d4e5f", "supi": "imsi-001010000000001", "session_type": 1, "method": "ecid", "status": 1, "qos_response_time_ms": 5000, "qos_horizontal_accuracy_m": 50, "last_result": { "locationEstimate": { "shape": "POINT_UNCERTAINTY_CIRCLE", "point": { "lat": 37.7749, "lon": -122.4194 }, "uncertainty": 50 }, "accuracyFulfilmentIndicator": "REQUESTED_ACCURACY_FULFILLED" }, "created_at": 1720000000, "updated_at": 1720000100 } } ``` ## Cancel a Positioning Session This path cancels an active positioning session. | Method | Path | | ------ | ------------------------------------- | | DELETE | `/api/beta/positioning/sessions/{id}` | ### Path Parameters | Name | Type | Description | | ---- | ------ | ------------------- | | `id` | string | Session identifier. | Returns `204 No Content` on success. ## List Cell Positions This path returns all provisioned cell positions. | Method | Path | | ------ | -------------------------- | | GET | `/api/beta/cell-positions` | ### Response Fields | Field | Type | Description | | ------------------------ | ------- | ------------------------------------------------------ | | `id` | string | Cell position identifier. | | `rat` | string | `nr` or `eutra`. | | `mcc` | string | Mobile Country Code. | | `mnc` | string | Mobile Network Code. | | `cell_identity` | string | Hex cell identity (NCI for NR, ECI for E-UTRA). | | `gnb_id` | string | gNB identifier. | | `latitude` | number | WGS-84 latitude in decimal degrees. | | `longitude` | number | WGS-84 longitude in decimal degrees. | | `altitude` | number | Altitude, in metres. | | `uncertainty_semi_major` | number | Semi-major axis of the uncertainty ellipse, in metres. | | `uncertainty_semi_minor` | number | Semi-minor axis of the uncertainty ellipse, in metres. | | `orientation_major` | integer | Orientation of the semi-major axis, in degrees. | | `confidence` | integer | Confidence, in percent. | | `source` | string | Origin of the record (e.g. `provisioned`). | ### Sample Response ``` { "result": [ { "id": "018f9f3a-4c2e-7bd1-9f21-0a1b2c3d4e5f", "rat": "nr", "mcc": "001", "mnc": "01", "cell_identity": "000000010", "latitude": 37.7749, "longitude": -122.4194, "source": "provisioned" } ] } ``` ## Create a Cell Position This path provisions the geographic position of a cell antenna. Cell ID lookups resolve a subscriber's serving cell to this position. | Method | Path | | ------ | -------------------------- | | POST | `/api/beta/cell-positions` | ### Parameters - `rat` (string): `nr` or `eutra`. - `mcc` (string): Mobile Country Code. - `mnc` (string): Mobile Network Code. - `cell_identity` (string): Hex cell identity (NCI for NR, ECI for E-UTRA). - `gnb_id` (string, optional): gNB identifier. - `latitude` (number): WGS-84 latitude in decimal degrees. Range `-90`…`90`. - `longitude` (number): WGS-84 longitude in decimal degrees. Range `-180`…`180`. - `altitude` (number, optional): Altitude, in metres. - `uncertainty_semi_major` (number, optional): Semi-major axis of the uncertainty ellipse, in metres. - `uncertainty_semi_minor` (number, optional): Semi-minor axis of the uncertainty ellipse, in metres. - `orientation_major` (integer, optional): Orientation of the semi-major axis, in degrees. - `confidence` (integer, optional): Confidence, in percent. ### Sample Request (E-UTRA) ``` { "rat": "eutra", "mcc": "001", "mnc": "01", "cell_identity": "0000001", "latitude": 37.7749, "longitude": -122.4194 } ``` ### Sample Response ``` { "result": { "message": "Cell position created", "id": "018f9f3a-4c2e-7bd1-9f21-0a1b2c3d4e5f" } } ``` ## Get a Cell Position This path returns a provisioned cell position. | Method | Path | | ------ | ------------------------------- | | GET | `/api/beta/cell-positions/{id}` | ### Path Parameters | Name | Type | Description | | ---- | ------ | ------------------------- | | `id` | string | Cell position identifier. | ### Sample Response ``` { "result": { "id": "018f9f3a-4c2e-7bd1-9f21-0a1b2c3d4e5f", "rat": "nr", "mcc": "001", "mnc": "01", "cell_identity": "000000010", "latitude": 37.7749, "longitude": -122.4194, "source": "provisioned" } } ``` ## Update a Cell Position This path updates a provisioned cell position. | Method | Path | | ------ | ------------------------------- | | PUT | `/api/beta/cell-positions/{id}` | ### Path Parameters | Name | Type | Description | | ---- | ------ | ------------------------- | | `id` | string | Cell position identifier. | ### Parameters - `rat` (string): `nr` or `eutra`. - `mcc` (string): Mobile Country Code. - `mnc` (string): Mobile Network Code. - `cell_identity` (string): Hex cell identity (NCI for NR, ECI for E-UTRA). - `gnb_id` (string, optional): gNB identifier. - `latitude` (number): WGS-84 latitude in decimal degrees. Range `-90`…`90`. - `longitude` (number): WGS-84 longitude in decimal degrees. Range `-180`…`180`. - `altitude` (number, optional): Altitude, in metres. - `uncertainty_semi_major` (number, optional): Semi-major axis of the uncertainty ellipse, in metres. - `uncertainty_semi_minor` (number, optional): Semi-minor axis of the uncertainty ellipse, in metres. - `orientation_major` (integer, optional): Orientation of the semi-major axis, in degrees. - `confidence` (integer, optional): Confidence, in percent. ### Sample Response ``` { "result": { "message": "Cell position updated" } } ``` ## Delete a Cell Position This path deletes a provisioned cell position. | Method | Path | | ------ | ------------------------------- | | DELETE | `/api/beta/cell-positions/{id}` | ### Path Parameters | Name | Type | Description | | ---- | ------ | ------------------------- | | `id` | string | Cell position identifier. | Returns `204 No Content` on success. # Metrics ## Get metrics This path returns the metrics of Ella core in Prometheus format. For more information about metrics exposed by Ella core, see the [Observability Reference](https://docs.ellanetworks.com/reference/observability/index.md). | Method | Path | | ------ | ----------------- | | GET | `/api/v1/metrics` | ### Parameters None ### Sample Response ``` # HELP app_database_storage_bytes The total storage used by the database in bytes. This is the size of the database file on disk. # TYPE app_database_storage_bytes gauge app_database_storage_bytes 28672 # HELP app_ip_addresses_allocated_total The total number of IP addresses currently allocated to subscribers # TYPE app_ip_addresses_allocated_total gauge app_ip_addresses_allocated_total 0 # HELP app_ip_addresses_total The total number of IP addresses available for subscribers # TYPE app_ip_addresses_total gauge app_ip_addresses_total 65792 # HELP go_gc_duration_seconds A summary of the wall-time pause (stop-the-world) duration in garbage collection cycles. # TYPE go_gc_duration_seconds summary go_gc_duration_seconds{quantile="0"} 2.2352e-05 go_gc_duration_seconds{quantile="0.25"} 3.29e-05 go_gc_duration_seconds{quantile="0.5"} 5.5266e-05 go_gc_duration_seconds{quantile="0.75"} 0.000135436 go_gc_duration_seconds{quantile="1"} 0.000371761 go_gc_duration_seconds_sum 0.011946775 go_gc_duration_seconds_count 134 # HELP go_gc_gogc_percent Heap size target percentage configured by the user, otherwise 100. This value is set by the GOGC environment variable, and the runtime/debug.SetGCPercent function. Sourced from /gc/gogc:percent # TYPE go_gc_gogc_percent gauge go_gc_gogc_percent 100 ... ``` # Data Networks ## List Data Networks This path returns the list of data networks. | Method | Path | | ------ | ---------------------------------- | | GET | `/api/v1/networking/data-networks` | ### Query Parameters | Name | In | Type | Default | Allowed | Description | | ---------- | ----- | ---- | ------- | ------- | ------------------------- | | `page` | query | int | `1` | `>= 1` | 1-based page index. | | `per_page` | query | int | `25` | `1…100` | Number of items per page. | ### Sample Response ``` { "result": { "items": [ { "name": "internet", "ipv4_pool": "172.250.0.0/24", "ipv6_pool": "2001:db8::/48", "dns": "8.8.8.8", "mtu": 1460, "status": { "sessions": 0 } } ], "page": 1, "per_page": 10, "total_count": 1 } } ``` ## Create a Data Network This path creates a new Data Network. | Method | Path | | ------ | ---------------------------------- | | POST | `/api/v1/networking/data-networks` | ### Parameters - `name` (string): The Name of the Data Network (dnn) - `ipv4_pool` (string, optional): The IPv4 pool of the data network in CIDR notation. Example: `172.250.0.0/24`. At least one of `ipv4_pool` or `ipv6_pool` must be provided. - `ipv6_pool` (string, optional): The IPv6 pool of the data network in CIDR notation. Example: `2001:db8::/48`. - `dns` (string): The IP address of the DNS server of the data network. Example: `8.8.8.8`. - `mtu` (integer): The MTU of the data network. Must be an integer between 1 and 65535. ### Sample Response ``` { "result": { "message": "Data Network created successfully" } } ``` ## Update a Data Network This path updates an existing data network. | Method | Path | | ------ | ----------------------------------------- | | PUT | `/api/v1/networking/data-networks/{name}` | ### Parameters - `ipv4_pool` (string, optional): The IPv4 pool of the data network in CIDR notation. Example: `172.250.0.0/24`. At least one of `ipv4_pool` or `ipv6_pool` must be provided. - `ipv6_pool` (string, optional): The IPv6 pool of the data network in CIDR notation. Example: `2001:db8::/48`. - `dns` (string): The IP address of the DNS server of the data network. Example: `8.8.8.8`. - `mtu` (integer): The MTU of the data network. Must be an integer between 1 and 65535. ### Sample Response ``` { "result": { "message": "Data Network updated successfully" } } ``` ## Get a Data Network This path returns the details of a specific data network. | Method | Path | | ------ | ----------------------------------------- | | GET | `/api/v1/networking/data-networks/{name}` | ### Parameters None ### Sample Response ``` { "result": { "name": "internet", "ipv4_pool": "0.0.0.0/24", "ipv6_pool": "2001:db8::/48", "dns": "8.8.8.8", "mtu": 1460, "status": { "sessions": 0 }, "ip_allocation": { "pool_size": 254, "allocated": 0, "available": 254 }, "ipv6_allocation": { "pool_size": 1, "allocated": 0, "available": 1 } } } ``` ## List IPv4 Allocations This path returns a paginated list of IPv4 address allocations (leases) for a specific data network. | Method | Path | | ------ | ---------------------------------------------------------- | | GET | `/api/v1/networking/data-networks/{name}/ipv4-allocations` | ### Query Parameters | Name | In | Type | Default | Allowed | Description | | ---------- | ----- | ---- | ------- | ------- | ------------------------- | | `page` | query | int | `1` | `>= 1` | 1-based page index. | | `per_page` | query | int | `25` | `1…100` | Number of items per page. | ### Sample Response ``` { "result": { "items": [ { "address": "172.250.0.1", "imsi": "001010100000001", "type": "dynamic", "session_id": 1 } ], "page": 1, "per_page": 25, "total_count": 1 } } ``` Each item contains the subscriber's assigned IPv4 address. ## List IPv6 Allocations This path returns a paginated list of IPv6 address allocations (leases) for a specific data network. | Method | Path | | ------ | ---------------------------------------------------------- | | GET | `/api/v1/networking/data-networks/{name}/ipv6-allocations` | ### Query Parameters | Name | In | Type | Default | Allowed | Description | | ---------- | ----- | ---- | ------- | ------- | ------------------------- | | `page` | query | int | `1` | `>= 1` | 1-based page index. | | `per_page` | query | int | `25` | `1…100` | Number of items per page. | ### Sample Response ``` { "result": { "items": [ { "address": "2001:db8::/64", "imsi": "001010100000001", "type": "dynamic", "session_id": 1 } ], "page": 1, "per_page": 25, "total_count": 1 } } ``` Each item contains the subscriber's assigned IPv6 /64 prefix. ## List Static IPs This path returns the static IP reservations for a specific data network. | Method | Path | | ------ | ---------------------------------------------------- | | GET | `/api/v1/networking/data-networks/{name}/static-ips` | ### Parameters None ### Sample Response ``` { "result": { "items": [ { "imsi": "001010100000001", "data_network": "internet", "ip_version": "ipv4", "address": "172.250.0.10", "status": "reserved", "session_id": null } ], "page": 1, "per_page": 1, "total_count": 1 } } ``` ## Create a Static IP This path pins an address to a subscriber on a data network. The IP version is inferred from the address family; IPv6 addresses must be /64-aligned. | Method | Path | | ------ | ---------------------------------------------------- | | POST | `/api/v1/networking/data-networks/{name}/static-ips` | ### Parameters - `imsi` (string): The IMSI of the subscriber to pin. - `address` (string): An IPv4 address or /64-aligned IPv6 prefix within the data network pool. Example: `172.250.0.10`. ### Sample Response ``` { "result": { "message": "Static IP created successfully" } } ``` ## Update a Static IP This path repins a subscriber's reservation to a new address. A change to a live session releases it so the UE re-establishes on the new address. | Method | Path | | ------ | ------------------------------------------------------------------------ | | PUT | `/api/v1/networking/data-networks/{name}/static-ips/{imsi}/{ip_version}` | ### Parameters - `address` (string): The new IPv4 address or /64-aligned IPv6 prefix within the data network pool. Example: `172.250.0.20`. ### Sample Response ``` { "result": { "message": "Static IP updated successfully" } } ``` ## Delete a Static IP This path removes a subscriber's static IP reservation. A change to a live session releases it so the UE re-establishes on a dynamic address. | Method | Path | | ------ | ------------------------------------------------------------------------ | | DELETE | `/api/v1/networking/data-networks/{name}/static-ips/{imsi}/{ip_version}` | ### Parameters None ### Sample Response ``` { "result": { "message": "Static IP deleted successfully" } } ``` ## List Framed Routes This path returns the framed routes for a specific data network, grouped by subscriber. A framed route is an IP prefix routed toward a subscriber's session, so a whole subnet behind the UE is reachable over one session. | Method | Path | | ------ | ------------------------------------------------------- | | GET | `/api/v1/networking/data-networks/{name}/framed-routes` | ### Parameters None ### Sample Response ``` { "result": { "items": [ { "imsi": "001010100000001", "ipv4": ["192.168.60.0/24"], "ipv6": ["fd00:60::/64"] } ], "page": 1, "per_page": 1, "total_count": 1 } } ``` ## Create Framed Routes This path sets a subscriber's framed-route set on a data network. It is rejected if the subscriber already has framed routes here (use the update path to replace them), if NAT is enabled, if a prefix overlaps a UE pool, a route, or another subscriber's framed route, or if the subscriber's profile does not bind the data network. At most 8 prefixes per family are allowed, and each is masked to its network form. | Method | Path | | ------ | ------------------------------------------------------- | | POST | `/api/v1/networking/data-networks/{name}/framed-routes` | ### Parameters - `imsi` (string): The IMSI of the subscriber. - `ipv4` (array of strings): IPv4 framed-route prefixes in CIDR form. Example: `["192.168.60.0/24"]`. - `ipv6` (array of strings): IPv6 framed-route prefixes in CIDR form. Example: `["fd00:60::/64"]`. ### Sample Response ``` { "result": { "message": "Framed routes created successfully" } } ``` ## Update Framed Routes This path replaces a subscriber's entire framed-route set on a data network; an empty set clears it. The same overlap and NAT rules as create apply. A change to a live session releases it so the UE re-establishes with the new routes. | Method | Path | | ------ | -------------------------------------------------------------- | | PUT | `/api/v1/networking/data-networks/{name}/framed-routes/{imsi}` | ### Parameters - `ipv4` (array of strings): The replacement IPv4 framed-route prefixes in CIDR form. - `ipv6` (array of strings): The replacement IPv6 framed-route prefixes in CIDR form. ### Sample Response ``` { "result": { "message": "Framed routes updated successfully" } } ``` ## Delete Framed Routes This path removes all framed routes for a subscriber on a data network. | Method | Path | | ------ | -------------------------------------------------------------- | | DELETE | `/api/v1/networking/data-networks/{name}/framed-routes/{imsi}` | ### Parameters None ### Sample Response ``` { "result": { "message": "Framed routes deleted successfully" } } ``` ## Delete a Data Network This path deletes a data network from Ella Core. | Method | Path | | ------ | ----------------------------------------- | | DELETE | `/api/v1/networking/data-networks/{name}` | ### Parameters None ### Sample Response ``` { "result": { "message": "Data Network deleted successfully" } } ``` # Interfaces ## Get Network Interfaces Config This path returns the network interfaces. | Method | Path | | ------ | ------------------------------- | | GET | `/api/v1/networking/interfaces` | ### Parameters None ### Sample Response ``` { "result": { "n2": { "addresses": ["192.168.40.6"], "port": 38412, "interface": "eth0" }, "n3": { "name": "wlp131s0", "addresses": ["192.168.40.6"], "external_address": "" }, "n6": { "name": "lo", "addresses": ["10.0.0.1"] }, "api": { "addresses": ["192.168.1.10"], "port": 5002 } } } ``` ## Update N3 Interface Settings This path updates the N3 interface settings. | Method | Path | | ------ | ---------------------------------- | | PUT | `/api/v1/networking/interfaces/n3` | ### Parameters - `external_address` (string): The external address to be used for the N3 / S1-U interface. This address is advertised to the radio in the GTP tunnel Transport Layer Address — to a gNB in the NGAP PDU Session Resource Setup Request (5G), or to an eNB in the S1AP E-RAB Setup (4G) — and the radio uses it to set up the GTP-U tunnel. This setting is useful when Ella Core is behind a proxy or NAT and the N3 / S1-U interface address is not reachable by the radio. If not set, Ella Core will use the address of the N3 interface as defined in the config file. ### Sample Response ``` { "result": { "message": "N3 interface updated" } } ``` # Routes ## List Routes This path returns the list of routes, including both user-configured static routes and BGP-learned routes. Each route includes a `source` field indicating its origin (`static` or `bgp`). | Method | Path | | ------ | --------------------------- | | GET | `/api/v1/networking/routes` | ### Query Parameters | Name | In | Type | Default | Allowed | Description | | ---------- | ----- | ---- | ------- | ------- | ------------------------- | | `page` | query | int | `1` | `>= 1` | 1-based page index. | | `per_page` | query | int | `25` | `1…100` | Number of items per page. | ### Sample Response ``` { "result": { "items": [ { "id": 0, "destination": "0.0.0.0/0", "gateway": "10.0.0.2", "interface": "n6", "metric": 200, "source": "bgp" }, { "id": 1, "destination": "10.0.0.0/24", "gateway": "203.0.113.1", "interface": "n6", "metric": 0, "source": "static" }, { "id": 2, "destination": "::/0", "gateway": "2001:db8::1", "interface": "n6", "metric": 200, "source": "bgp" }, { "id": 3, "destination": "fd45::/48", "gateway": "2001:db8:6::2", "interface": "n6", "metric": 0, "source": "static" } ], "page": 1, "per_page": 25, "total_count": 4 } } ``` ## Create a Route This path creates a new route. | Method | Path | | ------ | --------------------------- | | POST | `/api/v1/networking/routes` | ### Parameters - `destination` (string): The destination IP address of the route in CIDR notation. Examples: `0.0.0.0/0` (IPv4) or `::/0` (IPv6). - `gateway` (string): The IP address of the gateway of the route. Examples: `1.2.3.4` (IPv4) or `2001:db8::1` (IPv6). - `interface` (string): The outgoing interface of the route. Allowed values: `n3`, `n6`. - `metric` (int): The metric of the route. Must be a non-negative integer. ### Sample Response ``` { "result": { "message": "Route created successfully", "id": "4" } } ``` ## Get a Route This path returns the details of a specific route. | Method | Path | | ------ | -------------------------------- | | GET | `/api/v1/networking/routes/{id}` | ### Parameters None ### Sample Response ``` { "result": { "id": 4, "destination": "0.0.0.0/0", "gateway": "203.0.113.1", "interface": "n6", "metric": 0, "source": "static" } } ``` ## Delete a Route This path deletes a route from Ella Core. | Method | Path | | ------ | -------------------------------- | | DELETE | `/api/v1/networking/routes/{id}` | ### Parameters None ### Sample Response ``` { "result": { "message": "Route deleted successfully" } } ``` # NAT ## Get NAT Info This path returns the current NAT configuration. | Method | Path | | ------ | ------------------------ | | GET | `/api/v1/networking/nat` | ### Parameters None ### Sample Response ``` { "result": { "enabled": true } } ``` ## Update NAT Info This path updates the NAT configuration. | Method | Path | | ------ | ------------------------ | | PUT | `/api/v1/networking/nat` | ### Parameters - `enabled` (boolean): Enable or disable NAT. ### Sample Response ``` { "result": { "message": "NAT configuration updated successfully" } } ``` # Flow Accounting ## Get Flow Accounting Info This path returns the current flow accounting configuration. | Method | Path | | ------ | ------------------------------------ | | GET | `/api/v1/networking/flow-accounting` | ### Parameters None ### Sample Response ``` { "result": { "enabled": true } } ``` ## Update Flow Accounting Info This path updates the flow accounting configuration. | Method | Path | | ------ | ------------------------------------ | | PUT | `/api/v1/networking/flow-accounting` | ### Parameters - `enabled` (boolean): Enable or disable flow accounting. ### Sample Response ``` { "result": { "message": "Flow accounting settings updated successfully" } } ``` # BGP ## Get BGP Settings Returns the current BGP configuration. | Method | Path | | ------ | ------------------------ | | GET | `/api/v1/networking/bgp` | ### Parameters None ### Sample Response ``` { "result": { "enabled": true, "localAS": 64512, "routerID": "192.168.5.10", "listenAddress": ":179", "rejectedPrefixes": [ { "prefix": "127.0.0.0/8", "source": "builtin", "description": "IPv4 Loopback" }, { "prefix": "172.250.0.0/24", "source": "data_network", "description": "UE IP pool (internet)" }, { "prefix": "192.168.40.6/32", "source": "interface", "description": "N3 interface address" }, { "prefix": "192.168.5.0/24", "source": "interface", "description": "N6 interface subnet" } ] } } ``` The `rejectedPrefixes` array lists prefixes that are always rejected by the safety filter. These are derived from the N3 interface address, N6 interface subnets, data network IP pools, and built-in prefixes (link-local, loopback, multicast). They are read-only and cannot be configured. ## Update BGP Settings Updates the BGP configuration. Enabling BGP starts the embedded BGP speaker. Changing the local AS or router ID triggers a restart of the speaker. | Method | Path | | ------ | ------------------------ | | PUT | `/api/v1/networking/bgp` | ### Parameters - `enabled` (boolean): Enable or disable BGP. - `localAS` (integer): The local autonomous system number. - `routerID` (string): The BGP router ID (an IPv4 or IPv6 address). - `listenAddress` (string): The address and port to listen on (e.g. `:179`). ### Sample Response ``` { "result": { "message": "BGP settings updated successfully" } } ``` ## List BGP Peers Returns the list of configured BGP peers with live session status. | Method | Path | | ------ | ------------------------------ | | GET | `/api/v1/networking/bgp/peers` | ### Query Parameters | Name | In | Type | Default | Allowed | Description | | ---------- | ----- | ---- | ------- | ------- | ------------------------- | | `page` | query | int | `1` | `>= 1` | 1-based page index. | | `per_page` | query | int | `25` | `1…100` | Number of items per page. | ### Sample Response ``` { "result": { "items": [ { "id": 1, "address": "192.168.5.1", "remoteAS": 64513, "holdTime": 90, "hasPassword": true, "description": "upstream router", "importPrefixes": [ { "prefix": "0.0.0.0/0", "maxLength": 32 } ], "state": "established", "uptime": "1h23m45s", "prefixesSent": 3, "prefixesReceived": 2, "prefixesAccepted": 1 } ], "page": 1, "per_page": 25, "total_count": 1 } } ``` The `hasPassword` field indicates whether MD5 authentication is configured for the peer. The actual password is never returned by the API. The `state`, `uptime`, `prefixesSent`, `prefixesReceived`, and `prefixesAccepted` fields reflect the live BGP session status. They are empty/omitted when BGP is not running. The `uptime` field is only present when the session state is `established`. The `importPrefixes` field contains the per-peer import prefix list. When set to `[{"prefix": "0.0.0.0/0", "maxLength": 32}]`, all routes are accepted. An empty array means no routes are accepted from this peer. ## Get a BGP Peer Returns the details of a specific BGP peer. | Method | Path | | ------ | ----------------------------------- | | GET | `/api/v1/networking/bgp/peers/{id}` | ### Parameters None ### Sample Response ``` { "result": { "id": 1, "address": "192.168.5.1", "remoteAS": 64513, "holdTime": 90, "hasPassword": true, "description": "upstream router", "importPrefixes": [ { "prefix": "0.0.0.0/0", "maxLength": 32 } ], "state": "established", "uptime": "1h23m45s", "prefixesSent": 3, "prefixesReceived": 2, "prefixesAccepted": 1 } } ``` ## Create a BGP Peer Adds a new BGP peer. If BGP is running, the peer is added to the live speaker immediately. | Method | Path | | ------ | ------------------------------ | | POST | `/api/v1/networking/bgp/peers` | ### Parameters - `address` (string, required): The IPv4 or IPv6 address of the peer. - `remoteAS` (integer, required): The remote autonomous system number. - `holdTime` (integer): The BGP hold timer in seconds (default `90`, minimum `3`). Keepalive is derived as holdTime / 3. - `password` (string): MD5 authentication password. Omit or set to empty string for no authentication. - `description` (string): An optional description for the peer. - `importPrefixes` (array): List of prefix entries to accept from this peer. Each entry has `prefix` (CIDR string) and `maxLength` (integer). Use `[{"prefix": "0.0.0.0/0", "maxLength": 32}]` to accept all routes. Omit or set to `[]` to accept no routes. ### Sample Response ``` { "result": { "message": "BGP peer created successfully" } } ``` ## Update a BGP Peer Updates an existing BGP peer. If BGP is running, the peer is reconfigured in the live speaker. | Method | Path | | ------ | ----------------------------------- | | PUT | `/api/v1/networking/bgp/peers/{id}` | ### Parameters - `address` (string, required): The IPv4 or IPv6 address of the peer. - `remoteAS` (integer, required): The remote autonomous system number. - `holdTime` (integer): The BGP hold timer in seconds (default `90`, minimum `3`). - `password` (string): MD5 authentication password. - `description` (string): An optional description for the peer. - `importPrefixes` (array): List of prefix entries to accept from this peer. ### Sample Response ``` { "result": { "message": "BGP peer updated successfully" } } ``` ## Delete a BGP Peer Removes a BGP peer by ID. If BGP is running, the peer is removed from the live speaker immediately and any routes learned from that peer are withdrawn from the kernel. | Method | Path | | ------ | ----------------------------------- | | DELETE | `/api/v1/networking/bgp/peers/{id}` | ### Parameters None ### Sample Response ``` { "result": { "message": "BGP peer deleted successfully" } } ``` ## Get BGP Advertised Routes Returns the routes currently advertised to BGP peers (subscriber /32 routes). | Method | Path | | ------ | ------------------------------------------ | | GET | `/api/v1/networking/bgp/advertised-routes` | ### Parameters None ### Sample Response ``` { "result": { "routes": [ { "prefix": "10.45.0.3/32", "nextHop": "192.168.5.10", "subscriber": "001010100000001" } ] } } ``` Each route includes the `subscriber` IMSI that owns the IP address being advertised. ## Get BGP Learned Routes Returns the routes learned from BGP peers that passed the safety filter and import prefix list, and are currently installed in the kernel. | Method | Path | | ------ | --------------------------------------- | | GET | `/api/v1/networking/bgp/learned-routes` | ### Parameters None ### Sample Response ``` { "result": { "routes": [ { "prefix": "10.0.0.0/24", "nextHop": "192.168.5.1", "peer": "192.168.5.1" } ] } } ``` # Operator The Operator API provides endpoints to manage the Operator Information used to identify the operator - Operator ID (MCC, MNC), Tracking Information, Operator Code (OP), NAS Security Algorithms, and the Service Provider Name (SPN). ## Get Operator Information This path returns the complete operator information. This includes the Operator ID and Tracking Information. The Operator Code is never returned. | Method | Path | | ------ | ------------------ | | GET | `/api/v1/operator` | ### Parameters None ### Sample Response ``` { "result": { "id": { "mcc": "001", "mnc": "01" }, "tracking": { "supportedTacs": [ "001", "002", "003" ] }, "homeNetworkKeys": [ { "id": "0197b3a4-5c6d-7e8f-9a0b-1c2d3e4f5a6b", "keyIdentifier": 0, "scheme": "A", "publicKey": "021bd3c0ba857e6f45b6ecb76ad826fd27fecef441f23d0e418b645829261e16" } ], "nasSecurity": { "ciphering": ["AES", "SNOW3G", "NULL"], "integrity": ["AES", "SNOW3G", "NULL"] }, "spn": { "fullName": "Ella Networks", "shortName": "Ella" } } } ``` ## Update the Operator ID This path updates the operator ID. The Mobile Country Code (MCC) and Mobile Network Code (MNC) are used to identify the operator. The operator ID can't be changed when there are subscribers created in the system. | Method | Path | | ------ | --------------------- | | PUT | `/api/v1/operator/id` | ### Parameters - `mcc` (string): The Mobile Country Code (MCC) of the network. Must be a 3-digit string. - `mnc` (string): The Mobile Network Code (MNC) of the network. Must be a 2 or 3-digit string. ### Sample Response ``` { "result": { "message": "Operator ID updated successfully" } } ``` ## Update the Operator Tracking Information This path updates the operator tracking information. The Tracking Area Codes (TACs) are used to identify the tracking areas supported by the operator. Radios will need to be configured with one or more of these TACs to connect to the network. | Method | Path | | ------ | --------------------------- | | PUT | `/api/v1/operator/tracking` | ### Parameters - `supportedTacs` (array): An array of supported TACs (Tracking Area Codes). Each TAC must be a 6-character hex string. ### Sample Response ``` { "result": { "message": "Operator tracking information updated successfully" } } ``` ## Update the Operator Code (OP) This path updates the Operator Code (OP). The OP is a 32-character hexadecimal string that identifies the operator. This value is secret and should be kept confidential. The OP is used to create the derived Operator Code (OPc). The OP can't be changed when there are subscribers created in the system. | Method | Path | | ------ | ----------------------- | | PUT | `/api/v1/operator/code` | ### Parameters - `operatorCode` (string): The Operator Code (OP). Must be a 32-character hexadecimal string. ### Sample Response ``` { "result": { "message": "Operator Code updated successfully" } } ``` ## Create a Home Network Key Adds a new home network key for SUCI de-concealment. The key is identified by a (keyIdentifier, scheme) pair. Profile A keys use Curve25519 (X25519); Profile B keys use NIST P-256. Maximum 12 keys. | Method | Path | | ------ | ------------------------------------ | | POST | `/api/v1/operator/home-network-keys` | ### Parameters - `keyIdentifier` (integer): The key identifier. Must be between 0 and 255. Must match the value provisioned on the SIM/USIM. - `scheme` (string): The scheme. Must be `"A"` (Curve25519/X25519) or `"B"` (NIST P-256). - `privateKey` (string): The private key. Must be a 64-character hexadecimal string. ### Sample Response ``` { "result": { "message": "Home network key created successfully" } } ``` ## Get a Home Network Key's Private Key Returns the private key for a home network key. This is a sensitive operation that is recorded in the audit log. Only administrators and network managers can access this endpoint. | Method | Path | | ------ | ----------------------------------------------------- | | GET | `/api/v1/operator/home-network-keys/{id}/private-key` | ### Parameters - `id` (string, path): The UUID of the home network key. ### Sample Response ``` { "result": { "privateKey": "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789" } } ``` ## Delete a Home Network Key Removes a home network key. UEs using this key will no longer be able to register. | Method | Path | | ------ | ----------------------------------------- | | DELETE | `/api/v1/operator/home-network-keys/{id}` | ### Parameters - `id` (string, path): The UUID of the home network key. ### Sample Response ``` { "result": { "message": "Home network key deleted successfully" } } ``` ## Update the NAS Security Algorithms This path updates the NAS security algorithm preference order for ciphering and integrity protection. The order determines which algorithms the network prefers during subscriber device security capability negotiation. Changes take effect for the next subscriber registration. | Method | Path | | ------ | ------------------------------- | | PUT | `/api/v1/operator/nas-security` | ### Parameters - `ciphering` (array of strings): The preferred ciphering algorithm order. Each entry must be one of `NULL`, `SNOW3G`, or `AES`. At least one algorithm is required, maximum 3. No duplicates allowed. - `integrity` (array of strings): The preferred integrity algorithm order. Each entry must be one of `NULL`, `SNOW3G`, or `AES`. At least one algorithm is required, maximum 3. No duplicates allowed. These algorithm names are RAT-neutral: Ella Core signals them as NEA/NIA to 5G subscribers and as EEA/EIA to 4G subscribers (`NULL` → NEA0/EEA0, `SNOW3G` → NEA1/EEA1, `AES` → NEA2/EEA2). ### Sample Request ``` { "ciphering": ["AES", "SNOW3G"], "integrity": ["AES", "SNOW3G"] } ``` ### Sample Response ``` { "result": { "message": "Operator NAS security algorithms updated successfully" } } ``` ## Update the Service Provider Name (SPN) This path updates the network name (Service Provider Name) displayed on connected devices. Both the full and short names are encoded in the GSM 7-bit alphabet and sent to subscriber devices in the NAS Configuration Update Command. Changes take effect for the next subscriber registration. | Method | Path | | ------ | ---------------------- | | PUT | `/api/v1/operator/spn` | ### Parameters - `fullName` (string): The full network name shown on subscriber device displays. Must be between 1 and 50 characters. - `shortName` (string): An abbreviated network name. Must be between 1 and 50 characters. ### Sample Request ``` { "fullName": "Ella Networks", "shortName": "Ella" } ``` ### Sample Response ``` { "result": { "message": "Operator SPN updated successfully" } } ``` # Policies Policies define per-session QoS parameters for a specific (profile, slice, data network) combination. The Session AMBR caps the bitrate of a single PDU session and is enforced by Ella Core in the data plane. This is distinct from UE-AMBR (set on the profile), which caps aggregate throughput across all sessions and is enforced by the radio. ## List Policies This path returns the list of policies. | Method | Path | | ------ | ------------------ | | GET | `/api/v1/policies` | ### Query Parameters | Name | In | Type | Default | Allowed | Description | | -------------- | ----- | ---- | ------- | ------- | ------------------------- | | `page` | query | int | `1` | `>= 1` | 1-based page index. | | `per_page` | query | int | `25` | `1…100` | Number of items per page. | | `profile_name` | query | str | | | Filter by profile name. | ### Sample Response ``` { "result": { "items": [ { "name": "default", "profile_name": "enterprise", "slice_name": "default", "session_ambr_uplink": "200 Mbps", "session_ambr_downlink": "100 Mbps", "var5qi": 9, "arp": 1, "data_network_name": "internet", "default": true } ], "page": 1, "per_page": 10, "total_count": 1 } } ``` ## Create a Policy This path creates a new policy. Optionally, you can create network rules as part of the policy. | Method | Path | | ------ | ------------------ | | POST | `/api/v1/policies` | ### Parameters - `name` (string): The Name of the policy. - `profile_name` (string): The name of the profile associated with this policy. Must be the name of an existing profile. - `slice_name` (string): The name of the slice associated with this policy. Must be the name of an existing slice. - `session_ambr_uplink` (string): Maximum uplink bitrate for a single PDU session (Session AMBR). Enforced by Ella Core. Format: ` ` (e.g. `"100 Mbps"`). Allowed units: Kbps, Mbps, Gbps. - `session_ambr_downlink` (string): Maximum downlink bitrate for a single PDU session (Session AMBR). Enforced by Ella Core. Format: ` ` (e.g. `"200 Mbps"`). Allowed units: Kbps, Mbps, Gbps. - `var5qi` (integer): 5G QoS Identifier (5QI). Signaled to the radio, which uses it for scheduling (latency budget, error rate, priority); on a 4G radio it is signaled as the equivalent QCI. Valid values: 5, 6, 7, 8, 9, 69, 70, 79, 80 (non-GBR only). - `arp` (integer): Allocation and Retention Priority (1–15). Used by the radio at session setup for admission control and pre-emption decisions. Has no effect on traffic once the session is established. 1 = highest priority. - `data_network_name` (string): The name of the data network associated with the policy. Must be the name of an existing data network. - `default` (boolean, optional): Make this the profile's default APN/DNN binding (used for the 4G default bearer and 5G fallback). The first policy created in a profile becomes the default regardless. - `rules` (object, optional): Network rules to create with the policy, organized by direction. Rules are created in the order provided. ### Rules Object Structure The `rules` object contains: - `uplink` (array, optional): Array of uplink rules - `downlink` (array, optional): Array of downlink rules Each rule contains: - `description` (string): Description of the rule - `remote_prefix` (string, optional): IPv4 or IPv6 CIDR notation for remote prefix (e.g., "10.0.0.0/24" or "2001:db8::/32") or null. When omitted, matches any IP. - `protocol` (integer): Protocol number (0-255) - `port_low` (integer): Low port number (0-65535) - `port_high` (integer): High port number (0-65535) - `action` (string): "allow" or "deny" ### Sample Request with IPv4 Rules ``` { "name": "my-policy", "profile_name": "enterprise", "slice_name": "default", "session_ambr_uplink": "100 Mbps", "session_ambr_downlink": "200 Mbps", "var5qi": 9, "arp": 1, "data_network_name": "internet", "rules": { "uplink": [ { "description": "Allow HTTP/HTTPS", "protocol": 6, "port_low": 80, "port_high": 443, "action": "allow" }, { "description": "Deny all", "protocol": 0, "port_low": 0, "port_high": 0, "remote_prefix": "0.0.0.0/0", "action": "deny" } ], "downlink": [ { "description": "Allow DNS", "protocol": 17, "port_low": 53, "port_high": 53, "action": "allow" }, { "description": "Deny all", "protocol": 0, "port_low": 0, "port_high": 0, "remote_prefix": "0.0.0.0/0", "action": "deny" } ] } } ``` ### Sample Request with IPv6 Rules ``` { "name": "ipv6-policy", "profile_name": "enterprise", "slice_name": "default", "session_ambr_uplink": "100 Mbps", "session_ambr_downlink": "200 Mbps", "var5qi": 9, "arp": 1, "data_network_name": "internet", "rules": { "uplink": [ { "description": "Allow HTTPS to IPv6", "protocol": 6, "port_low": 443, "port_high": 443, "remote_prefix": "2001:db8::/32", "action": "allow" }, { "description": "Deny all IPv6", "protocol": 0, "port_low": 0, "port_high": 0, "remote_prefix": "::/0", "action": "deny" } ], "downlink": [] } } ``` ### Sample Response ``` { "result": { "message": "Policy created successfully" } } ``` ## Update a Policy This path updates an existing policy. Network rules are always replaced on every update call. | Method | Path | | ------ | ------------------------- | | PUT | `/api/v1/policies/{name}` | ### Parameters - `profile_name` (string): The name of the profile associated with this policy. Must be the name of an existing profile. - `slice_name` (string): The name of the slice associated with this policy. Must be the name of an existing slice. - `session_ambr_uplink` (string): Maximum uplink bitrate for a single PDU session (Session AMBR). Enforced by Ella Core. Format: ` ` (e.g. `"100 Mbps"`). Allowed units: Kbps, Mbps, Gbps. The ceiling depends on the access the policy's profile permits: when 5G is allowed the number must not exceed 65535 in the chosen unit (TS 24.501 §9.11.4.14); when 4G is allowed the rate must not exceed 10 Gbps (TS 24.008 §10.5.6.5B). - `session_ambr_downlink` (string): Maximum downlink bitrate for a single PDU session (Session AMBR). Enforced by Ella Core. Format: ` ` (e.g. `"200 Mbps"`). Allowed units: Kbps, Mbps, Gbps. The ceiling depends on the access the policy's profile permits: when 5G is allowed the number must not exceed 65535 in the chosen unit (TS 24.501 §9.11.4.14); when 4G is allowed the rate must not exceed 10 Gbps (TS 24.008 §10.5.6.5B). - `var5qi` (integer): 5G QoS Identifier (5QI). Signaled to the radio, which uses it for scheduling (latency budget, error rate, priority); on a 4G radio it is signaled as the equivalent QCI. Valid values: 5, 6, 7, 8, 9, 69, 70, 79, 80 (non-GBR only). - `arp` (integer): Allocation and Retention Priority (1–15). Used by the radio at session setup for admission control and pre-emption decisions. Has no effect on traffic once the session is established. 1 = highest priority. - `data_network_name` (string): The name of the data network associated with the policy. Must be the name of an existing data network. - `default` (boolean, optional): Make this the profile's default APN/DNN binding, clearing the previous default. Omitted or false leaves it unchanged. - `rules` (object, optional): Network rules to set on the policy. Existing rules are always deleted first. If this field is omitted, all existing rules are deleted. ### Rules Behavior - **Omit `rules` field**: all existing rules are deleted. - **Provide `rules` with arrays**: existing rules are deleted and replaced with the new ones. - **Provide empty arrays**: all existing rules are deleted and no new rules are created. To keep existing rules, you must re-supply them in every update request. ### Sample Request to Update Rules ``` { "session_ambr_uplink": "100 Mbps", "session_ambr_downlink": "200 Mbps", "var5qi": 9, "arp": 1, "data_network_name": "internet", "rules": { "uplink": [ { "description": "Allow SSH", "protocol": 6, "port_low": 22, "port_high": 22, "action": "allow" }, { "description": "Deny all", "protocol": 0, "port_low": 0, "port_high": 0, "remote_prefix": "0.0.0.0/0", "action": "deny" } ], "downlink": [] } } ``` ### Sample Request to Delete All Rules Omit the `rules` field (or provide empty arrays) to delete all existing rules: ``` { "session_ambr_uplink": "100 Mbps", "session_ambr_downlink": "200 Mbps", "var5qi": 9, "arp": 1, "data_network_name": "internet" } ``` ### Sample Response ``` { "result": { "message": "Policy updated successfully" } } ``` ## Get a Policy This path returns the details of a specific policy, including any associated network rules. | Method | Path | | ------ | ------------------------- | | GET | `/api/v1/policies/{name}` | ### Parameters None ### Sample Response with Rules ``` { "result": { "name": "my-policy", "profile_name": "enterprise", "slice_name": "default", "session_ambr_uplink": "10 Mbps", "session_ambr_downlink": "10 Mbps", "var5qi": 9, "arp": 1, "data_network_name": "internet", "default": false, "rules": { "uplink": [ { "description": "Allow HTTP/HTTPS", "protocol": 6, "port_low": 80, "port_high": 443, "action": "allow" }, { "description": "Deny all", "protocol": 0, "port_low": 0, "port_high": 0, "remote_prefix": "0.0.0.0/0", "action": "deny" } ], "downlink": [ { "description": "Allow DNS", "protocol": 17, "port_low": 53, "port_high": 53, "action": "allow" }, { "description": "Deny all", "protocol": 0, "port_low": 0, "port_high": 0, "remote_prefix": "0.0.0.0/0", "action": "deny" } ] } } } ``` ### Sample Response without Rules If a policy has no associated rules, the `rules` field will be omitted: ``` { "result": { "name": "simple-policy", "profile_name": "enterprise", "slice_name": "default", "session_ambr_uplink": "10 Mbps", "session_ambr_downlink": "10 Mbps", "var5qi": 9, "arp": 1, "data_network_name": "internet", "default": false } } ``` ## Delete a Policy This path deletes a policy from Ella Core. | Method | Path | | ------ | ------------------------- | | DELETE | `/api/v1/policies/{name}` | ### Parameters None ### Sample Response ``` { "result": { "message": "Policy deleted successfully" } } ``` # Pprof Ella Core exposes a [pprof](https://pkg.go.dev/net/http/pprof) compatible API for profiling analysis. Profiling endpoints are only available to admin users and scraping requires an API token. ## Index This endpoint returns an HTML page listing the available profiles. | Method | Path | | ------ | ---------------- | | GET | `/api/v1/pprof/` | ## Allocs This endpoint returns a sampling of historical memory allocations over the life of the program. | Method | Path | | ------ | ---------------------- | | GET | `/api/v1/pprof/allocs` | ## Block This endpoint returns a sampling of goroutine blocking events. | Method | Path | | ------ | --------------------- | | GET | `/api/v1/pprof/block` | ## Cmdline This endpoint returns the command line invocation of the program. | Method | Path | | ------ | ----------------------- | | GET | `/api/v1/pprof/cmdline` | ## Goroutine This endpoint returns a stack trace of all current goroutines. | Method | Path | | ------ | ------------------------- | | GET | `/api/v1/pprof/goroutine` | ## Heap This endpoint returns a sampling of memory allocations of live objects. | Method | Path | | ------ | -------------------- | | GET | `/api/v1/pprof/heap` | ## Mutex This endpoint returns a sampling of mutex contention events. | Method | Path | | ------ | --------------------- | | GET | `/api/v1/pprof/mutex` | ## Profile This endpoint returns a 30-second CPU profile. | Method | Path | | ------ | ----------------------- | | GET | `/api/v1/pprof/profile` | ## Threadcreate This endpoint returns a sampling of thread creation events. | Method | Path | | ------ | ---------------------------- | | GET | `/api/v1/pprof/threadcreate` | ## Trace This endpoint returns a 1-second execution trace. | Method | Path | | ------ | --------------------- | | GET | `/api/v1/pprof/trace` | ## Symbol This endpoint is used to look up program counter (PC) addresses and return symbol information (for example, function names). It is primarily used by pprof tooling to map raw addresses in profiles back to human-readable symbols. | Method | Path | | ------ | ---------------------- | | POST | `/api/v1/pprof/symbol` | # Profiles Profiles define the subscriber's Aggregate Maximum Bit Rate (UE-AMBR), which caps the total non-GBR throughput across **all** of a subscriber's sessions. Ella Core signals this value to the radio, which enforces it. A profile can be shared across multiple policies. ## List Profiles This path returns the list of profiles. | Method | Path | | ------ | ------------------ | | GET | `/api/v1/profiles` | ### Query Parameters | Name | In | Type | Default | Allowed | Description | | ---------- | ----- | ---- | ------- | ------- | ------------------------- | | `page` | query | int | `1` | `>= 1` | 1-based page index. | | `per_page` | query | int | `25` | `1…100` | Number of items per page. | ### Sample Response ``` { "result": { "items": [ { "name": "enterprise", "ue_ambr_uplink": "1 Gbps", "ue_ambr_downlink": "1 Gbps", "allow_4g": true, "allow_5g": true } ], "page": 1, "per_page": 10, "total_count": 1 } } ``` ## Create a Profile This path creates a new profile. | Method | Path | | ------ | ------------------ | | POST | `/api/v1/profiles` | ### Parameters - `name` (string): The name of the profile. - `ue_ambr_uplink` (string): Aggregate uplink bitrate cap across all of the subscriber's sessions (UE-AMBR). Enforced by the radio. Format: ` ` (e.g. `"500 Mbps"`). Allowed units: Kbps, Mbps, Gbps. - `ue_ambr_downlink` (string): Aggregate downlink bitrate cap across all of the subscriber's sessions (UE-AMBR). Enforced by the radio. Format: ` ` (e.g. `"1 Gbps"`). Allowed units: Kbps, Mbps, Gbps. - `allow_4g` (boolean, optional): Whether subscribers using this profile may attach over 4G (EPC). Defaults to `true`. - `allow_5g` (boolean, optional): Whether subscribers using this profile may register over 5G (5GC). Defaults to `true`. ### Sample Response ``` { "result": { "message": "Profile created successfully" } } ``` ## Get a Profile This path returns the details of a specific profile. | Method | Path | | ------ | ------------------------- | | GET | `/api/v1/profiles/{name}` | ### Parameters None ### Sample Response ``` { "result": { "name": "enterprise", "ue_ambr_uplink": "1 Gbps", "ue_ambr_downlink": "1 Gbps", "allow_4g": true, "allow_5g": true } } ``` ## Update a Profile This path updates an existing profile. | Method | Path | | ------ | ------------------------- | | PUT | `/api/v1/profiles/{name}` | ### Parameters - `ue_ambr_uplink` (string): Aggregate uplink bitrate cap across all of the subscriber's sessions (UE-AMBR). Enforced by the radio. Format: ` ` (e.g. `"500 Mbps"`). Allowed units: Kbps, Mbps, Gbps. The ceiling depends on the access the profile permits: 10 Gbps when 4G is allowed (S1AP `BitRate`, TS 36.413), otherwise 4 Tbps (NGAP `BitRate`, TS 38.413). - `ue_ambr_downlink` (string): Aggregate downlink bitrate cap across all of the subscriber's sessions (UE-AMBR). Enforced by the radio. Format: ` ` (e.g. `"1 Gbps"`). Allowed units: Kbps, Mbps, Gbps. The ceiling depends on the access the profile permits: 10 Gbps when 4G is allowed (S1AP `BitRate`, TS 36.413), otherwise 4 Tbps (NGAP `BitRate`, TS 38.413). - `allow_4g` (boolean, optional): Whether subscribers using this profile may attach over 4G (EPC). Defaults to `true`. - `allow_5g` (boolean, optional): Whether subscribers using this profile may register over 5G (5GC). Defaults to `true`. ### Sample Response ``` { "result": { "message": "Profile updated successfully" } } ``` ## Delete a Profile This path deletes a profile from Ella Core. A profile cannot be deleted if it is referenced by any policy. | Method | Path | | ------ | ------------------------- | | DELETE | `/api/v1/profiles/{name}` | ### Parameters None ### Sample Response ``` { "result": { "message": "Profile deleted successfully" } } ``` # Radios Radios are automatically added to Ella Core as they connect to the network as long as they are configured to use the same Tracking Area Code (TAC), Mobile Country Code (MCC), and Mobile Network Code (MNC) as Ella Core. The Radio API provides endpoints to view information about connected radios. ## List Radios This path returns the list of radios in the inventory. | Method | Path | | ------ | -------------------- | | GET | `/api/v1/ran/radios` | ### Query Parameters | Name | In | Type | Default | Allowed | Description | | ---------- | ----- | ---- | ------- | ------- | ------------------------- | | `page` | query | int | `1` | `>= 1` | 1-based page index. | | `per_page` | query | int | `25` | `1…100` | Number of items per page. | ### Response Fields | Field | Type | Description | | ---------------- | ------ | ------------------------------------------------------------------------------------------------------------------- | | `name` | string | Radio name. | | `id` | string | Radio identifier. | | `address` | string | Radio address. | | `type` | string | Radio type: `gNB`, `ng-eNB`, `eNB`, `N3IWF`, or `Unknown`. | | `supported_tais` | array | **Deprecated.** Use [Get a Radio](#get-a-radio) for supported TAIs. This field will be removed in a future release. | ### Sample Response ``` { "result": { "items": [ { "name": "gnb1", "id": "001:01:000102", "address": "10.1.107.203/192.168.251.5:9487", "type": "gNB", "supported_tais": [] } ], "page": 1, "per_page": 10, "total_count": 1 } } ``` ## Get a Radio This path returns the details of a specific radio, including connection timestamps, RAN node type, and supported tracking areas. To list subscribers connected to this radio, use `GET /api/v1/subscribers?radio={name}`. | Method | Path | | ------ | --------------------------- | | GET | `/api/v1/ran/radios/{name}` | ### Path Parameters | Name | Type | Description | | ------ | ------ | ----------- | | `name` | string | Radio name. | ### Sample Response ``` { "result": { "name": "gnb1", "id": "001:01:000102", "address": "10.1.107.203/192.168.251.5:9487", "connected_at": "2025-08-12T16:58:00Z", "last_seen_at": "2025-08-12T17:02:30Z", "type": "gNB", "supported_tais": [ { "tai": { "plmnID": { "mcc": "001", "mnc": "01" }, "tac": "000001" }, "snssais": [ { "sst": 1, "sd": "102030" } ] }, { "tai": { "plmnID": { "mcc": "123", "mnc": "12" }, "tac": "000002" }, "snssais": [ { "sst": 1, "sd": "102031" } ] } ] } } ``` ## List Radio Events This path returns the list of radio events. | Method | Path | | ------ | -------------------- | | GET | `/api/v1/ran/events` | ### Query Parameters | Name | In | Type | Default | Allowed | Description | | ---------------- | ----- | ---- | ------- | ----------------- | ----------------------------------------------------------------------------------------------- | | `page` | query | int | `1` | `>= 1` | 1-based page index. | | `per_page` | query | int | `25` | `1…100` | Number of items per page. | | `protocol` | query | str | | NGAP, S1AP | Filter by protocol (`NGAP` for 5G radios, `S1AP` for 4G radios). | | `direction` | query | str | | inbound, outbound | Filter by log direction. | | `message_type` | query | str | | | Filter by message type. | | `timestamp_from` | query | str | | | Filter logs from this timestamp (inclusive). RFC3339 format (e.g., 2006-01-02T15:04:05Z07:00). | | `timestamp_to` | query | str | | | Filter logs up to this timestamp (inclusive). RFC3339 format (e.g., 2006-01-02T15:04:05Z07:00). | ### Sample Response ``` { "result": { "items": [ { "id": 1, "timestamp": "2025-08-12T16:58:00.810-0400", "radio": "gnb1", "address": "10.1.107.203:9487", "protocol": "NGAP", "message_type": "PDU Session Establishment Accept", "direction": "inbound", "raw": "ABUAOQAABAAbAAkAAPEQMAASNFAAUkAMBIBnbmIwMDEyMzQ1AGYAEAAAAAABAADxEAAAEAgQIDAAFUABQA", "details": "{\"pduSessionID\":1}" } ], "page": 1, "per_page": 10, "total_count": 1 } } ``` ## Get Radio Event This path returns a specific radio event by its ID. | Method | Path | | ------ | ------------------------- | | GET | `/api/v1/ran/events/{id}` | ### Sample Response ``` { "result": { "raw": "IBUALAAABAABAAUBAGFtZgBgAAgAAADxEMr+AABWQAH/AFAACwAA8RAAABAIECAw", "decoded": { "successful_outcome": { "procedure_code": "NGSetup", "criticality": "Reject (0)", "value": { "ng_setup_response": { "ies": [ { "id": "AMFName (1)", "criticality": "Reject (0)", "amf_name": "amf" }, { "id": "ServedGUAMIList (96)", "criticality": "Reject (0)", "served_guami_list": [ { "plmn_id": { "mcc": "001", "mnc": "01" }, "amf_id": "cafe00" } ] }, { "id": "RelativeAMFCapacity (86)", "criticality": "Ignore (1)", "relative_amf_capacity": 255 }, { "id": "PLMNSupportList (80)", "criticality": "Reject (0)", "plmn_support_list": [ { "plmn_id": { "mcc": "001", "mnc": "01" }, "slice_support_list": [ { "sst": 1, "sd": "102030" } ] } ] } ] } } } } } } ``` ## Update Radio Event Retention Policy This path updates the radio event retention policy. | Method | Path | | ------ | ------------------------------ | | PUT | `/api/v1/ran/events/retention` | ### Parameters - `days` (integer): The number of days to retain radio events. Must be a positive integer. ### Sample Response ``` { "result": { "message": "Radio event retention policy updated successfully" } } ``` ## Clear Radio Events This path deletes all radio events. | Method | Path | | ------ | -------------------- | | DELETE | `/api/v1/ran/events` | ### Parameters None ### Sample Response ``` { "result": { "message": "All radio events cleared successfully" } } ``` ## Get Radio Event Retention Policy This path returns the current radio event retention policy. | Method | Path | | ------ | ------------------------------ | | GET | `/api/v1/ran/events/retention` | ### Parameters None ### Sample Response ``` { "result": { "days": 30 } } ``` # Restore This path restores the database from a provided backup file. The backup file must be uploaded as part of the request. Standalone only Online restore is available in standalone deployments only. Clustered deployments reject this endpoint with `409 Conflict`; use the offline `restore.bundle` disaster-recovery flow instead. See [Backup and Restore](https://docs.ellanetworks.com/how_to/backup_and_restore/index.md). ## Restore a Backup | Method | Path | | ------ | ----------------- | | POST | `/api/v1/restore` | ### Parameters - `backup` (file): The backup file to restore the database from. It must be a valid backup of the database. ### Sample Response ``` { "result": { "message": "Database restored successfully" } } ``` ### Error Responses - `409 Conflict` — returned in HA mode. Body: ``` { "error": "Online restore is disabled in HA mode. Stop the cluster and seed a fresh node from the backup archive via the restore.bundle drop-in path; see the backup and restore docs." } ``` # Slices Slices represent S-NSSAI (Single Network Slice Selection Assistance Information) configurations. Each slice defines a Slice Service Type (SST) and an optional Slice Differentiator (SD). Ella Core uses slice information alongside the data network name to determine which policies apply to a subscriber's session. ## List Slices This path returns the list of slices. | Method | Path | | ------ | ---------------- | | GET | `/api/v1/slices` | ### Query Parameters | Name | In | Type | Default | Allowed | Description | | ---------- | ----- | ---- | ------- | ------- | ------------------------- | | `page` | query | int | `1` | `>= 1` | 1-based page index. | | `per_page` | query | int | `25` | `1…100` | Number of items per page. | ### Sample Response ``` { "result": { "items": [ { "name": "default", "sst": 1, "sd": "010203" } ], "page": 1, "per_page": 10, "total_count": 1 } } ``` ## Create a Slice This path creates a new slice. | Method | Path | | ------ | ---------------- | | POST | `/api/v1/slices` | ### Parameters - `name` (string): The name of the slice. - `sst` (integer): The Slice Service Type (SST). Must be an integer between 1 and 255. - `sd` (optional string): The Service Differentiator (SD). Must be a 3-byte hexadecimal string without the "0x" prefix. Ex. "010203". ### Sample Response ``` { "result": { "message": "Slice created successfully" } } ``` ## Get a Slice This path returns the details of a specific slice. | Method | Path | | ------ | ----------------------- | | GET | `/api/v1/slices/{name}` | ### Parameters None ### Sample Response ``` { "result": { "name": "default", "sst": 1, "sd": "010203" } } ``` ## Update a Slice This path updates an existing slice. | Method | Path | | ------ | ----------------------- | | PUT | `/api/v1/slices/{name}` | ### Parameters - `sst` (integer): The Slice Service Type (SST). Must be an integer between 1 and 255. - `sd` (optional string): The Service Differentiator (SD). Must be a 3-byte hexadecimal string without the "0x" prefix. Ex. "010203". ### Sample Response ``` { "result": { "message": "Slice updated successfully" } } ``` ## Delete a Slice This path deletes a slice from Ella Core. A slice cannot be deleted if it is referenced by any policy. | Method | Path | | ------ | ----------------------- | | DELETE | `/api/v1/slices/{name}` | ### Parameters None ### Sample Response ``` { "result": { "message": "Slice deleted successfully" } } ``` # Status ## Get the status This path returns the status of Ella core. | Method | Path | | ------ | ---------------- | | GET | `/api/v1/status` | ### Parameters None ### Response Headers When clustering is enabled, the response includes an `X-Ella-Role` header with the Raft role of the responding node (`Leader`, `Follower`, or `Candidate`). Load balancers can use this header to direct write traffic to the leader. ### Fields Top‑level (always present): - `version` (string): Software version. - `revision` (string): Git commit hash. - `initialized` (boolean): True once the system has at least one user. - `ready` (boolean): True once the node has completed full startup. - `schemaVersion` (integer): Shared‑database schema version this binary expects. Reported in both standalone and HA modes. - `datapathAttachMode` (string): Mechanism the data plane attached with — `xdp-native`, `tcx`, or `xdp-generic`. The nested `cluster` object is present only when HA is enabled: - `enabled` (boolean): Always `true` inside this object. - `role` (string): Raft role of this node — `Leader`, `Follower`, or `Candidate`. - `nodeId` (integer): Raft node ID of this instance. - `isLeader` (boolean): Convenience — `role == "Leader"`. - `leaderNodeId` (integer): Raft node ID of the current leader; zero when unknown. - `leaderAPIAddress` (string): HTTP API URL of the current leader. Omitted when the leader is unknown. - `appliedIndex` (integer): Last Raft log index applied by this node. - `clusterId` (string): Cluster ID from the operator configuration. Omitted when not set. ### Sample Response ``` { "result": { "version": "v1.15.0", "revision": "388ce92244a0b304e9f6c15e3f896acee6fe7b1a", "initialized": true, "ready": true, "schemaVersion": 9, "datapathAttachMode": "xdp-native" } } ``` When clustering is enabled, the response includes a `cluster` object: ``` { "result": { "version": "v1.15.0", "revision": "388ce92244a0b304e9f6c15e3f896acee6fe7b1a", "initialized": true, "ready": true, "schemaVersion": 9, "datapathAttachMode": "xdp-native", "cluster": { "enabled": true, "role": "Leader", "nodeId": 1, "isLeader": true, "leaderNodeId": 1, "appliedIndex": 42, "clusterId": "my-cluster", "leaderAPIAddress": "https://10.0.0.1:5002" } } } ``` # Subscribers This section describes the RESTful API for managing network subscribers. Network subscribers are the devices that connect to the private mobile network. ## List Subscribers This path returns the list of network subscribers. | Method | Path | | ------ | --------------------- | | GET | `/api/v1/subscribers` | ### Query Parameters | Name | In | Type | Default | Allowed | Description | | -------------- | ----- | ---- | ------- | ------- | -------------------------------------------------------------------------------- | | `page` | query | int | `1` | `>= 1` | 1-based page index. | | `per_page` | query | int | `25` | `1…100` | Number of items per page. | | `radio` | query | str | | | Filter by radio name. Returns only subscribers connected to the specified radio. | | `data_network` | query | str | | | Filter by data network name. Returns only subscribers whose profile reaches it. | ### Sample Response ``` { "result": { "items": [ { "imsi": "001010100007487", "profile_name": "default", "status": { "registered": true, "radio_access_types": ["5G"], "num_sessions": 1 } } ], "page": 1, "per_page": 10, "total_count": 1 } } ``` ## Create a Subscriber This path creates a new network subscriber. | Method | Path | | ------ | --------------------- | | POST | `/api/v1/subscribers` | ### Parameters - `imsi` (string): The IMSI of the subscriber. Must be a 15-digit string starting with ``. - `key` (string): The key of the subscriber. Must be a 32-character hexadecimal string. - `sequenceNumber` (string): The sequence number of the subscriber. Must be a 6-byte hexadecimal string. - `profile_name` (string): The profile name of the subscriber. Must be the name of an existing profile. - `opc` (optional string): The operator code of the subscriber. If not provided, it will be generated automatically using the Operator Code (OP) and the `key` parameter. ### Sample Response ``` { "result": { "message": "Subscriber created successfully" } } ``` ## Update a Subscriber This path updates an existing network subscriber. | Method | Path | | ------ | ---------------------------- | | PUT | `/api/v1/subscribers/{imsi}` | ### Parameters - `profile_name` (string): The profile name of the subscriber. ### Sample Response ``` { "result": { "message": "Subscriber updated successfully" } } ``` ## Get a Subscriber This path returns the details of a specific network subscriber. | Method | Path | | ------ | ---------------------------- | | GET | `/api/v1/subscribers/{imsi}` | ### Parameters None ### Sample Response ``` { "result": { "imsi": "001010100007487", "profile_name": "default", "status": { "registered": true, "radio_access_types": ["5G"], "imei": "359881234567890", "ciphering_algorithm": "SNOW3G", "integrity_algorithm": "SNOW3G", "last_seen_at": "2026-03-16T12:34:56Z", "last_seen_radio": "gNB-1" }, "sessions": [ { "radio_access_type": "5G", "id": 1, "status": "active", "ip_type": "IPv4v6", "ipv4_address": "10.45.0.2", "ipv6_prefix": "2001:db8::/64", "data_network": "internet", "slice": { "sst": 1, "sd": "000001" }, "ambr_uplink": "100 Mbps", "ambr_downlink": "200 Mbps" } ] } } ``` ## Get Subscriber Credentials This path returns the authentication credentials for a specific subscriber. The response includes the subscriber's permanent key, OPc, and sequence number. This is the preferred way to retrieve credentials and replaces the deprecated fields on the List and Get responses. An audit log entry is created each time credentials are viewed. | Method | Path | | ------ | ---------------------------------------- | | GET | `/api/v1/subscribers/{imsi}/credentials` | ### Parameters None ### Sample Response ``` { "result": { "key": "5122250214c33e723a5dd523fc145fc0", "opc": "981d464c7c52eb6e5036234984ad0bcf", "sequenceNumber": "16f3b3f70fc7" } } ``` ## Delete a Subscriber This path deletes a subscriber from Ella Core. | Method | Path | | ------ | ---------------------------- | | DELETE | `/api/v1/subscribers/{imsi}` | ### Parameters None ### Sample Response ``` { "result": { "message": "Subscriber deleted successfully" } } ``` # Support Bundle Generate a support bundle containing system diagnostics, configuration, and database-derived JSON to help with debugging. Sensitive fields (for example private keys) are redacted where possible; however you should inspect the bundle contents before sharing it with Ella Networks support. ## Generate Support Bundle | Method | Path | | ------ | ------------------------ | | POST | `/api/v1/support-bundle` | ### Parameters None ### Response On success the server returns `200` with the body containing a gzipped tar archive and a `Content-Disposition` header recommending a filename like `ella-support-.tar.gz`. The response Content-Type is `application/gzip`. The archive contains a best-effort collection of relevant diagnostics (database-derived JSON exports, YAML configuration files, system/network diagnostics, and eBPF maps data). The bundle is intended to be inspected locally before sharing. ## Bundle Contents ### Database and Configuration - `db.json`: Database export containing operator configuration, policies, data networks, and subscriber information (with sensitive fields redacted) - `config.yaml`: Runtime configuration file - `system/`: System information including version, OS release, kernel version, memory, CPU, disk space, and network diagnostics ### eBPF Maps The bundle includes eBPF map data in a `bpf/` directory (best-effort): - Each map is exported as compressed NDJSON (`mapname.ndjson.gz`) with decoded key/value entries using generated Go struct types for accurate field representation - Each map includes a corresponding `mapname_metadata.json` file containing: - Map name, type (Hash, Array, RingBuf, etc.), key/value sizes - Number of entries reported and whether entries were truncated - Snapshot timestamp - Any error encountered during export **Default configuration:** - **Excluded maps**: `nat_ct`, `flow_stats` (excluded due to potentially large size) - **Max entries per map**: `10000` (entries beyond this limit are truncated with `truncated: true`) - **Ring buffer maps**: Automatically skipped (cannot be iterated); includes `no_neigh_map` and `nocp_map` ringbuf variant If BPF map export fails, an error file `bpf/error.txt` is included in the bundle instead. # Usage This section describes the RESTful API for managing subscriber usage data. ## Get Subscriber Usage This path retrieves usage data for network subscribers. | Method | Path | | ------ | -------------------------- | | GET | `/api/v1/subscriber-usage` | ### Query Parameters | Name | In | Type | Default | Allowed | Description | | ------------ | ----- | ------ | ---------- | ------------------- | --------------------------------------------------------------------- | | `start` | query | string | `now-7d` | | Start date for usage data. Format: YYYY-MM-DD. | | `end` | query | string | `now` | | End date for usage data. Format: YYYY-MM-DD. | | `group_by` | query | string | *required* | `day`, `subscriber` | Grouping method for usage data. Required — omitting it returns `400`. | | `subscriber` | query | string | \`\` | | Filter usage data for a specific subscriber. | ### Sample Response ``` { "result": [ { "2025-02-22": { "uplink_bytes": 1048576, "downlink_bytes": 2097152, "total_bytes": 3145728 } }, { "2025-02-23": { "uplink_bytes": 524288, "downlink_bytes": 1048576, "total_bytes": 1572864 } } ] } ``` ## Clear Subscriber Usage This path clears usage data for all network subscribers. | Method | Path | | ------ | -------------------------- | | DELETE | `/api/v1/subscriber-usage` | ### Sample Response ``` { "result": { "message": "All subscriber usage cleared successfully" } } ``` ## Get Subscriber Usage Retention Policy This path returns the current subscriber usage retention policy. | Method | Path | | ------ | ------------------------------------ | | GET | `/api/v1/subscriber-usage/retention` | ### Sample Response ``` { "result": { "days": 365 } } ``` ## Update Subscriber Usage Retention Policy This path updates the subscriber usage retention policy. | Method | Path | | ------ | ------------------------------------ | | PUT | `/api/v1/subscriber-usage/retention` | ### Parameters - `days` (integer): The number of days to retain subscriber usage data. Must be a positive integer. ### Sample Response ``` { "result": { "message": "Subscriber usage retention policy updated successfully" } } ``` # Users This section describes the RESTful API for managing system users. System users are used to authenticate with Ella Core and manage the system. ## List Users This path returns the list of system users. | Method | Path | | ------ | --------------- | | GET | `/api/v1/users` | ### Query Parameters | Name | In | Type | Default | Allowed | Description | | ---------- | ----- | ---- | ------- | ------- | ------------------------- | | `page` | query | int | `1` | `>= 1` | 1-based page index. | | `per_page` | query | int | `25` | `1…100` | Number of items per page. | ### Sample Response ``` { "result": { "items": [ { "email": "admin@ellanetworks.com", "role_id": 1 } ], "page": 1, "per_page": 10, "total_count": 1 } } ``` ## Create a User This path creates a new system user. | Method | Path | | ------ | --------------- | | POST | `/api/v1/users` | ### Parameters - `email` (string): The email of the user. - `password` (string): The password of the user. - `role_id` (int): The role ID of the user. Allowed values: - 1 (admin): Administrator user with full access to network and system resources. - 2 (read only): Read-only user with only read access to network resources. - 3 (network manager): Network manager user with full access to network resources. ### Sample Response ``` { "result": { "message": "User created successfully" } } ``` ## Update a User This path updates an existing system user. | Method | Path | | ------ | ----------------------- | | PUT | `/api/v1/users/{email}` | ### Parameters - `role_id` (int): The role of the user. Allowed values: - 1 (admin): Administrator user with full access to network and system resources. - 2 (read only): Read-only user with only read access to network resources. - 3 (network manager): Network manager user with full access to network resources. ### Sample Response ``` { "result": { "message": "User updated successfully" } } ``` ## Get a User This path returns the details of a specific system user. | Method | Path | | ------ | ----------------------- | | GET | `/api/v1/users/{email}` | ### Parameters None ### Sample Response ``` { "result": { "email": "admin@ellanetworks.com", "role_id": 1 } } ``` ## Delete a User This path deletes a user from Ella Core. | Method | Path | | ------ | ----------------------- | | DELETE | `/api/v1/users/{email}` | ### Parameters None ### Sample Response ``` { "result": { "message": "User deleted successfully" } } ``` ## Update My Password This path updates the password of the currently authenticated user. The user must provide their current password for verification. After a successful password change, all existing sessions for the user are invalidated. | Method | Path | | ------ | --------------------------- | | PUT | `/api/v1/users/me/password` | ### Parameters - `current_password` (string): The user's current password. - `password` (string): The new password. ### Sample Response ``` { "result": { "message": "User password updated successfully" } } ``` ## Update a User Password This path updates the password of a specific system user. After a successful password change, all existing sessions for the target user are invalidated. This path requires admin privileges. | Method | Path | | ------ | -------------------------------- | | PUT | `/api/v1/users/{email}/password` | ### Parameters - `password` (string): The new password of the user. ### Sample Response ``` { "result": { "message": "User password updated successfully" } } ``` ## Create an API Token This path creates a new API token for the authenticated user. The API token can be used to authenticate with Ella Core's RESTful API. The API token will have the same permissions as your user account. Actions performed with the token will be logged under your user account. | Method | Path | | ------ | ----------------------------- | | POST | `/api/v1/users/me/api-tokens` | ### Parameters - `name` (string): The name of the API token. - `expires_at` (string, optional): The expiration date of the API token in RFC 3339 format. If not provided, the token will never expire. ### Sample Response ``` { "result": { "token": "ellacore_Xl2yU1rcy2BP_8q5iOpNBtoXLYdwddbBCHInx" } } ``` Note The API token is only returned once when created. Make sure to copy it and store it securely. ## List API Tokens This path returns the list of API tokens for the authenticated user. | Method | Path | | ------ | ----------------------------- | | GET | `/api/v1/users/me/api-tokens` | ### Query Parameters | Name | In | Type | Default | Allowed | Description | | ---------- | ----- | ---- | ------- | ------- | ------------------------- | | `page` | query | int | `1` | `>= 1` | 1-based page index. | | `per_page` | query | int | `25` | `1…100` | Number of items per page. | ### Sample Response ``` { "result": { "items": [ { "id": "Xl2yU1rcy2BP", "name": "My Token", "expires_at": "2024-12-31T23:59:59Z" } ], "page": 1, "per_page": 25, "total_count": 1 } } ``` ## Delete an API Token This path deletes an API token for the authenticated user. | Method | Path | | ------ | ---------------------------------- | | DELETE | `/api/v1/users/me/api-tokens/{id}` | ### Parameters None ### Sample Response ``` { "result": { "message": "API token deleted successfully" } } ``` ## List a User's API Tokens (Admin) This path returns a paginated list of API tokens belonging to the specified user. Requires admin privileges. | Method | Path | | ------ | ---------------------------------- | | GET | `/api/v1/users/{email}/api-tokens` | ### Query Parameters | Name | In | Type | Default | Allowed | Description | | ---------- | ----- | ---- | ------- | ------- | ------------------------- | | `page` | query | int | `1` | `>= 1` | 1-based page index. | | `per_page` | query | int | `25` | `1…100` | Number of items per page. | ### Sample Response ``` { "result": { "items": [ { "id": "Xl2yU1rcy2BP", "name": "CI Pipeline", "expires_at": "2026-12-31T23:59:59Z" } ], "page": 1, "per_page": 25, "total_count": 1 } } ``` ## Create an API Token for a User (Admin) This path creates a new API token for the specified user. The token will have the same permissions as the target user's account. Actions performed with the token will be logged under the target user's account. Requires admin privileges. Maximum 12 tokens per user. | Method | Path | | ------ | ---------------------------------- | | POST | `/api/v1/users/{email}/api-tokens` | ### Parameters - `name` (string): The name of the API token (3–50 characters). - `expires_at` (string, optional): The expiration date of the API token in RFC 3339 format. If not provided, the token will never expire. ### Sample Response ``` { "result": { "token": "ellacore_Ab3cD4eFgHiJ_9x8wVuTsRqPoNmLkJiHgFeDcBa" } } ``` Note The API token is only returned once when created. Make sure to copy it and store it securely. ## Delete a User's API Token (Admin) This path deletes an API token belonging to the specified user. Requires admin privileges. | Method | Path | | ------ | --------------------------------------- | | DELETE | `/api/v1/users/{email}/api-tokens/{id}` | ### Parameters None ### Sample Response ``` { "result": { "message": "API token deleted successfully" } } ``` # Explanation # Explanation The following explanations provide context and clarification on key-topics related to the use and configuration of Ella Core. Refer to these explanations to refine your understanding of Ella Core and the operation of Private mobile networks. - [Architecture](https://docs.ellanetworks.com/explanation/architecture/index.md) - [High Availability](https://docs.ellanetworks.com/explanation/high_availability/index.md) - [Platform Security](https://docs.ellanetworks.com/explanation/platform_security/index.md) - [Subscriber Security](https://docs.ellanetworks.com/explanation/subscriber_security/index.md) - [User Plane Packet Processing with eBPF](https://docs.ellanetworks.com/explanation/user_plane_packet_processing_with_ebpf/index.md) - [Obtaining a PLMN ID for a Private Network](https://docs.ellanetworks.com/explanation/obtaining_plmn_id/index.md) - [Managing SIM Cards](https://docs.ellanetworks.com/explanation/managing_sim_cards/index.md) - [Advertising and receiving routes via BGP](https://docs.ellanetworks.com/explanation/bgp/index.md) # The Ella Core Architecture Explained ## What is a Mobile Network? A mobile network connects wireless devices to each other and to the internet. Mobile Network Overview ### Components of a Mobile Network A mobile network is broadly divided into two major components: the Core Network and the Radio Access Network (RAN). #### 1. Core Network The Core is the heart of a mobile network. It is designed to manage all data and signaling flows within the network. Key Functions of the mobile Core: - **Authentication and Security**: Ensures that devices and users are authenticated and data transmission is secure. - **Session Management**: Establishes and maintains user sessions to provide stable data communication. - **Policy**: Implements quality of service (QoS) rules. - **Data Network Access**: Connects user devices to external data networks like the internet or private networks. #### 2. Radio Access Network (RAN) The Radio Access Network (RAN) is the interface between user devices and the core network. It is responsible for transmitting and receiving signals over the air, enabling connectivity between the device and the Core. Key Components of the RAN - **Base Station**: The base station that connects user equipment (UE) to the core network. - **Antennas and Spectrum**: Handles communication over radio frequencies. ## The problem with typical Mobile Core Networks Typical 4G and 5G Core networks are designed around a set of specialized components, collectively known as network functions (NFs). Each Network Function serves a specific role. Examples of key 5G network functions include: - **Access and Mobility Management Function (AMF)**: Handles registration, connection, and mobility management for user devices. - **Session Management Function (SMF)**: Manages session lifecycle and assigns IP addresses to devices. - **User Plane Function (UPF)**: Forwards data packets to and from user devices, enabling data transfer between the device and the external network. - **Authentication Server Function (AUSF)**: Verifies the identity of users and devices. - **Network Slice Selection Function (NSSF)**: Allocates resources for network slicing. These Network Functions are typically deployed as separate microservices, leveraging a distributed architecture for flexibility, scalability, and fault tolerance. However, this approach comes with significant complexity and overhead, including the need for extensive orchestration, inter-service communication, and resource management. This complexity results in high operational costs, that can be prohibitive for private deployments. A Typical 5G core architecture ## The Ella Core approach Ella Core was designed with the specific needs of private mobile networks in mind. For these users, simplicity, reliability, and security are what counts most. To address these requirements, we made the conscious decision to combine all 4G and 5G network functions into a single application. Users only interact with Ella Core through a web-based user interface or a REST API, both of which are built into the application. The Ella Core approach ### Network Functions are abstracted away Ella Core still performs the same functions as traditional mobile networks, and behind the scenes, it implements the same 3GPP standards and protocols. However, HTTP communication between Network Functions is replaced with in-process function calls. This design greatly simplifies the deployment and operation of the Core network, as there is no need to manage multiple services, databases, or network connections. ### The database is embedded Persistent data is stored in an embedded database, eliminating the need for managing external databases. This design choice allows for a simple deployment process, backup and restore functionality, and high availability through Raft-based clustering. # Advertising and receiving routes via BGP ## What is BGP? BGP is a protocol that allows exchanging routing information between autonomous systems. ## When is BGP needed? Subscriber devices receive IPs from the data network pool. When NAT is not used, the external network needs to know how to route packets back to the subscriber through Ella Core. BGP might be needed in enterprise deployments where routable subscriber IPs are required. ## How does BGP work in Ella Core? ### Advertise subscriber routes Ella Core embeds a BGP speaker that automatically advertises a `/32` (IPv4) or `/64` (IPv6) route for each active subscriber: 1. A subscriber establishes a session and receives an IP address (IPv4, e.g. `10.45.0.3`) or an IPv6 prefix (e.g. `2001:db8:ad50:8500::/64`). 1. Ella Core announces the route `10.45.0.3/32` (or `2001:db8:ad50:8500::/64` for IPv6) to all configured BGP peers, with the N6 interface address as the next-hop. 1. Upstream routers install the route, and return traffic flows through the N6 interface to Ella Core, which delivers it to the subscriber over GTP-U. 1. When the session is released, Ella Core withdraws the route. This means routing state always reflects the set of currently connected subscribers with no manual intervention. Framed routes are advertised the same way, from the node anchoring the session, and withdrawn on release. ### Receive routes from BGP peers Ella Core receives routes from BGP peers and installs them into the kernel routing table. This allows operators to manage routes (e.g., a default route via an upstream router) through BGP instead of static routes. ### In an HA cluster Each node runs its own BGP speaker and advertises `/32` (IPv4) or `/64` (IPv6) prefixes for the sessions it currently hosts. See [High Availability](https://docs.ellanetworks.com/explanation/high_availability/index.md) for the broader cluster model. # High Availability High availability (HA) lets you run an Ella Core cluster so that the network keeps working when nodes fail. Each node is active and can accept radios and subscriber traffic. HA is designed around the [Raft Consensus Algorithm](https://raft.github.io/): at any time one node is the leader, it is the only node that accepts writes, and every write replicates to a majority of nodes before it is considered committed. Nodes communicate together via mTLS to share changes. High Availability in Ella Core ## What HA covers Deploy three or five nodes. A *voter* is a node that counts toward quorum, and a quorum is a majority of voters: 2 of 3, or 3 of 5. Three nodes tolerate one failure; five nodes tolerate two. Within those bounds, surviving voters keep accepting writes, radio traffic, and operator changes with no manual intervention. Two things HA does not handle automatically: - **Loss of quorum.** If more than half the voters fail at the same time, the cluster loses quorum and writes stall until enough nodes return — or the cluster is restored from backup via [Disaster recovery](#disaster-recovery). - **In-flight UE sessions.** Sessions on a dead node drop; those UEs re-register on a surviving node. ## What replicates, and what does not Network-wide resources — subscribers, profiles, policies, slices, data networks, network rules, IP leases, users, API tokens, audit logs, the operator configuration — replicate across the cluster. Every replicated record carries a globally-unique ID, so rows created on different nodes never collide. If a node dies, the survivors hold the same state, automatically elect a new leader, and keep accepting writes. Per-node configuration does not replicate. This covers the local data-plane and routing settings each node owns: static routes, BGP settings, BGP peers and import prefixes, NAT, the N3 external address, and flow accounting. To configure these on an HA cluster, hit each node's API directly — a change made on one node does not propagate to its peers. This lets nodes in different racks or AZs run with different upstream gateways and BGP topologies. Runtime state tied to a specific connection or session also does not replicate: SCTP associations with radios, UE contexts, active sessions and their User Plane state, GTP-U tunnels, and active BGP adjacencies. Observability is per-node: each instance exposes its own Prometheus endpoint, radio events, and flow reports, so operators scrape every node for a cluster-wide view. ## User plane and routing A UE's user-plane traffic flows through the node that handled its registration — that node runs its User Plane and terminates its GTP-U tunnel. Each data network has one cluster-wide IP pool; the replicated lease table guarantees no two UEs receive the same address, and each lease records the node currently serving it. When BGP is enabled, each node advertises a `/32` route for every UE session it hosts (see [Advertising routes via BGP](https://docs.ellanetworks.com/explanation/bgp/index.md)). When a UE re-registers on a different node after failover, the lease's owning node is updated in place — the UE keeps its IP — and the new node's speaker begins advertising the same `/32` from its N6. The dead node's BGP session times out after the hold timer (90 s by default, configurable per peer), its routes are withdrawn, and upstream routing converges on the survivor without operator action. ## Failover and timing Leader re-election completes within a few seconds; surviving nodes continue accepting NGAP, S1AP, and API calls the whole time. Each Ella Core node presents as a distinct AMF in the same AMF Set (5G) and a distinct MME — a distinct GUMMEI — in a single MME Pool (4G). A UE's GUTI pins it to the node that handled its registration, and new UEs distribute across the nodes by advertised capacity. When a node dies, radios detect the loss via SCTP heartbeat timeout and reselect a surviving AMF/MME. UEs that were attached to the dead node then re-register from scratch, including a fresh authentication and a new session. ## Deployment scenarios The HA cluster is the same regardless of how radios connect to it; the radio side determines how much HA reaches individual UEs. ### Radios Connected to Every Node (AMF Set / MME Pool) When a Core dies, radios reselect within the Set/Pool automatically; affected UEs re-register on a surviving node without operator action. Radios Connected to Every Node (AMF Set / MME Pool) ### Radios Pinned to Specific Nodes Useful for site- or tenant-partitioned deployments. Network-wide state still replicates, so subscribers and policies stay consistent across nodes — but if a Core dies, its paired radios lose connectivity to the core and must be reconfigured to reach a surviving node. UE failover is manual, not automatic. Radios Pinned to Specific Nodes ## Draining a node Draining prepares a node for removal without disrupting traffic on its peers. A drained node hands Raft leadership to another voter if it held it, signals connected radios that it is unavailable so new UEs attach elsewhere, and stops advertising user-plane routes so upstream routing shifts to the survivors. Existing flows keep running until the node is removed or shut down. Drain is triggered by an operator via the cluster API. Removal requires a drained node. ## Scaling the cluster A new node joins as a voter by default: once it has exchanged its join token and started, the leader adds it directly to the voting set. If you would rather have the node catch up on the Raft log before it counts toward quorum, set `initial-suffrage: nonvoter` in its config — it then joins as a non-voter and Autopilot promotes it to a voter automatically once it has been healthy and up-to-date for a short stabilization window (or you can promote it immediately via the promote endpoint). Shrinking is symmetric. Drain the node, then remove it; the remaining voters continue serving writes while the configuration change commits. ## Inter-node communication using mTLS Every inter-node connection is mutually authenticated over TLS. Each node owns a long-lived, self-signed cluster certificate. To admit a new node, an admin mints a single-use join token from the Cluster page; the node presents it once to register itself, after which every voter accepts its connections. Certificates are scoped to a single cluster, so credentials from one cluster cannot authenticate into another. Removing a node immediately revokes its access cluster-wide. ## Disaster recovery HA clusters recover from total loss through an offline, backup-driven path. An operator stops every node, seeds one node from a backup archive, and starts it — it comes up as a single-voter cluster carrying the restored state. The remaining voters then rejoin with fresh join tokens. The backup archive carries the cluster's identity and its trusted-node registrations, so the restored leader trusts itself immediately; freshly-joined voters re-register through the standard join flow. The step-by-step procedure lives in [Backup and Restore](https://docs.ellanetworks.com/how_to/backup_and_restore/index.md). ## Rolling upgrades Upgrades proceed one node at a time: drain the node, refresh its binary, then resume. Each node retains its node-id, certificate, and Raft membership across the swap. Writes continue throughout; the cluster is briefly mixed-version during each step. When the new binary carries schema changes, the cluster keeps running on the old schema until every member of the Raft configuration reports support live via its cluster-internal status endpoint; only then does the migration commit through Raft. Skip-version upgrades (`vN → vN+2`) and downgrades are not supported. ## Further reading - [Deploy a High Availability Cluster](https://docs.ellanetworks.com/how_to/deploy_ha_cluster/index.md) — step-by-step guide to bring up a cluster. - [Scale Up a High Availability Cluster](https://docs.ellanetworks.com/how_to/scale_up_ha_cluster/index.md) — add nodes to an existing cluster. - [Perform a Rolling Upgrade](https://docs.ellanetworks.com/how_to/rolling_upgrade/index.md) — upgrade every node without taking the cluster offline. - [Cluster API reference](https://docs.ellanetworks.com/reference/api/cluster/index.md) — cluster management endpoints. # Managing SIM Cards As a network operator, you will need to provision a SIM card for each subscriber you create in Ella Core. There are two main approaches to managing SIM cards in a private mobile network. ## Using Physical SIM Cards Physical SIM cards can be used in devices that support them. You will need to obtain blank SIM cards and program them with the subscriber information (IMSI, Key, OPc) corresponding to the subscribers you create in Ella Core. ### Obtaining SIM Cards You can obtain physical SIM cards from a SIM card vendor (ex. [Sysmocom Programmable SIM Cards](https://sysmocom.de/products/sim/sysmoisim-sja5/index.html)). ### Burning SIM Cards You can burn the SIM card using a card reader/writer (ex. [OmniKey 3121](https://www.hidglobal.com/products/omnikey-3121)) along with software provided by the SIM card vendor. The software will allow you to input the subscriber information (IMSI, Key, OPc) and write it to the SIM card. For example, using Osmocom's [pysim](https://github.com/osmocom/pysim) software, you can burn a SIM card with the following command: ``` export IMSI=001018435063221 export KEY=525c8e65e8449a7067c1ca4367098c60 export OPC=a5db238bfaa2c9f01704332378f10f65 export MCC=001 export MNC=01 export ADMIN_CODE=76543210 ./pySim-prog.py -p0 -n Ella -t sysmoISIM-SJA5 -i $IMSI -c $MCC -x $MCC -y $MNC -o $OPC -k $KEY -a $ADMIN_CODE -j 1 ``` SUCI (5G only) SUCI concealment is a 5G feature; 4G does not use it. Some devices like iPhones also require the Home Network Public Key to be programmed on the SIM card. When provisioning SUCI support, the SIM must be configured with the **protection scheme** (Profile A or Profile B), the **Key Identifier**, and the corresponding **public key** — all of which must match a home network key configured in Ella Core. You can find the public key on the Operator page in the UI or via the Operator API. If you are using PySim, please refer to the [SUCI Concealement documentation](https://downloads.osmocom.org/docs/pysim/master/html/suci-tutorial.html). ## Using eSIM eSIMs (embedded SIMs) allow for remote provisioning of SIM profiles. This can simplify the management of SIM cards, especially in large-scale deployments. You can use an eSIM management platform to create and manage subscriber profiles, which can then be downloaded to eSIM-enabled devices. # Obtaining a PLMN ID for a Private Network ## Introduction to PLMN IDs A Public Land Mobile Network (PLMN) ID is a globally unique identifier that allows mobile devices to recognize and connect to mobile networks. In private mobile networks, having a correct PLMN ID is critical for ensuring proper network identification and operation. A PLMN ID consists of two parts: - **Mobile Country Code (MCC):** A three-digit code that identifies the country. - **Mobile Network Code (MNC):** A two- or three-digit code that identifies the network operator within that country. Each subscriber in a mobile network has a unique identifier called the International Mobile Subscriber Identity (IMSI). The IMSI is composed of the PLMN ID and a subscriber-specific identifier. ## How PLMN IDs Are Assigned PLMN IDs are regulated by the [International Telecommunication Union (ITU)](https://www.itu.int/en/Pages/default.aspx). Because there is a limited pool of available PLMN IDs, obtaining one through the ITU can be challenging, particularly for private network operators. ## Options for Private Networks There are several approaches available for private networks to acquire a PLMN ID: 1. **Using the Reserved PLMN ID:** The Mobile Country Code **999** is globally reserved for private networks. Network operators can choose an MNC (e.g., **01**, **123**) to create a PLMN ID such as **999-01** or **999-123**. This method is ideal if your network does not require a globally unique identifier. 1. **National Authority Assignments:** In some countries, a designated national regulatory body assigns PLMN IDs to private networks. If your country uses this approach, you must apply for a PLMN ID through the appropriate national channels. 1. **Alliance for Private Networks:** The Alliance for Private Networks offers a [Network Identifier Program](https://www.mfa-tech.org/network-identifier-program/#:~:text=The%20PLMN%20ID%20identifies%20a,in%20any%20available%20spectrum%20today) that provides the temporary use of PLMN IDs for private networks. This program allows private network operators to obtain a "slice" of a PLMN ID. They ensure that subscribers from different private networks are uniquely identified and do not overlap. 1. **ITU Assignments:** For private networks that require a globally unique identifier, a PLMN ID can be obtained via the ITU process. This typically involves coordination with your local regulatory authority and compliance with international standards. ## Note on Configuration Once you have obtained a PLMN ID for your private network, you can configure it in Ella Core via the [Operator API](https://docs.ellanetworks.com/reference/api/operator/index.md) or the user interface. The PLMN ID can only be updated when no subscribers are created. # Platform Security Info To report a security vulnerability, please file a [Private Security Report](https://github.com/ellanetworks/core/security). Security is one of Ella Core's core tenets. From authentication and authorization to transport encryption and audit logging, security is built into every layer of the system. ## Authentication & Authorization Ella Core enforces authentication on API requests towards most endpoints. Two authentication methods are supported: - **Session-based authentication.** Users authenticate with email and password. A session cookie and a short-lived access token are issued. The login endpoint enforces per-IP rate limiting to protect against brute-force attacks. - **API tokens.** Per-user tokens with explicit expiry that can be revoked individually. Recommended for programmatic access. ### Role-Based Access Control Every request is authorized against a role-based permission system with three built-in roles: | Role | Scope | | ------------------- | ---------------------------------------------------------------------------------------------- | | **Admin** | Full access to all resources and operations. | | **Network Manager** | Manages network resources (subscribers, policies, data networks, routes). Cannot manage users. | | **Read Only** | Read-only access to network resources. | ## Secret Storage - **User passwords** are stored as one-way hashes. Verification uses constant-time comparison to prevent timing attacks. - **API token secrets** are stored as one-way hashes. The raw token is returned only once at creation time and is never retrievable afterward. - **Session tokens** are cryptographically random values. Only a one-way hash is persisted. - **JWT signing secret** is a cryptographically random value generated once and stored in the database. It is never exposed through the API. Rotating it invalidates all previously issued tokens and sessions. Warning Database backups contain the full database, including subscriber secrets (Key, OPc, SQN) and the JWT signing secret. Store and transfer backups encrypted and treat them as admin credentials. ## Transport Security Ella Core uses TLS to secure its API and web interface. The TLS configuration is defined in the [configuration file](https://docs.ellanetworks.com/reference/config_file/index.md). The snap installation generates a self-signed certificate (valid for 365 days) by default. Users can replace the certificate and key files at any time; a service restart applies the change. For production deployments, replace the self-signed certificate with one issued by a trusted Certificate Authority (CA) and restrict access to the private key. Ella Core supports TLS `1.2` and `1.3`. In a [high-availability](https://docs.ellanetworks.com/explanation/high_availability/index.md) cluster, inter-node communication is secured with mutual TLS (TLS `1.3`) using fingerprint-pinned, per-node self-signed certificates. ## Minimal Attack Surface Ella Core minimizes its attack surface through minimal packaging: - **Container image.** Built on a distroless base with no operating system layer, shell, or package manager. Only the strictly necessary runtime dependencies are included. Image size: **under 80 MB**. - **Snap.** Ships only the application binary and a minimal configuration file. Package size: **under 20 MB**. ## Audit Logging Ella Core logs security-relevant events as audit records that can be accessed via the UI and the API. These logs provide a comprehensive record of who did what and when on your network, helping you monitor activity, investigate incidents, and meet compliance requirements. Each audit record contains: | Field | Description | | ------------- | -------------------------------------------------------- | | **Timestamp** | RFC 3339 UTC timestamp. | | **Actor** | Email of the user who performed the action. | | **Action** | Machine-readable action identifier (e.g., `auth_login`). | | **IP** | Client IP address. | | **Details** | Human-readable description. | ### Retention Audit logs are retained for **7 days** by default. The retention period is configurable through the [Audit Logs API](https://docs.ellanetworks.com/reference/api/audit_logs/index.md). A background worker runs every 24 hours and deletes records older than the configured retention period. # Subscriber Security Info To report a security vulnerability, please file a [Private Security Report](https://github.com/ellanetworks/core/security). Ella Core implements **5G-AKA** (5G) and **EPS-AKA** (4G) (Authentication and Key Agreement) for secure, mutual authentication between the subscriber's device and the network. The subscriber's Universal Subscriber Identity Module (USIM) stores the identity and credentials required for authentication: - **IMSI (International Mobile Subscriber Identity)**: A globally unique identifier for the subscriber. - **Key (Subscriber's Secret Key)**: A 128-bit cryptographic key shared between the USIM and the network. - **OPc (Operator Code)**: A value derived from the operator key (OP) and the subscriber's secret key (K) using the Milenage algorithm. - **SQN (Sequence Number)**: A counter maintained by both the USIM and the network to prevent replay attacks. ## Subscriber Privacy (SUCI) - 5G Only Ella Core supports **SUCI** (Subscription Concealed Identifier) to protect subscriber identity over the air. The IMSI is encrypted by the subscriber's device before transmission using ECIES (Elliptic Curve Integrated Encryption Scheme). The network decrypts the SUCI to recover the SUPI. This prevents IMSI-catching attacks. Two protection profiles are supported: | Profile | Curve | SUCI Scheme ID | | ------------- | ---------------------- | -------------- | | **Profile A** | Curve25519 (X25519) | 1 | | **Profile B** | NIST P-256 (secp256r1) | 2 | Home network keys can be managed through the [Operator API](https://docs.ellanetworks.com/reference/api/operator/index.md) or the Operator page in the UI. ## NAS Security After authentication, the network and the subscriber's device negotiate ciphering and integrity algorithms. Once established, these algorithms protect **all NAS signaling** for the lifetime of the connection. Administrators configure a single, RAT-neutral set of ciphering and integrity algorithms — **NULL**, **SNOW 3G**, and **AES** — and their priority order through the [Operator API](https://docs.ellanetworks.com/reference/api/operator/index.md) or the Operator page in the UI. Ella Core applies them under the appropriate 3GPP names per radio technology: | Algorithm | 5G | 4G | | --------- | ----------- | ----------- | | NULL | NEA0 / NIA0 | EEA0 / EIA0 | | SNOW 3G | NEA1 / NIA1 | EEA1 / EIA1 | | AES | NEA2 / NIA2 | EEA2 / EIA2 | Warning Null algorithms (NEA0/NIA0 on 5G, EEA0/EIA0 on 4G) provide no security protection. Only enable them for testing or device compatibility. ## Managing Subscriber Credentials Users can update the Operator Key (OP) via the [Operator API](https://docs.ellanetworks.com/reference/api/operator/index.md) or the UI. When creating a new subscriber via the [Subscribers API](https://docs.ellanetworks.com/reference/api/subscribers/index.md) or the UI, Ella Core automatically computes the OPc using the provided Key and the current OP value. The UI provides a user-friendly interface for automatically generating IMSIs, Keys, and SQNs for new subscribers. # Data Plane Packet processing with eBPF This document explains the key concepts behind packet Ella Core's subscriber data packet processing, between the **N3 / S1-U** and **N6 / SGi** interfaces. It covers the components, workflow, and technologies used in the data plane. ## eBPF, XDP, and TCX [eBPF](https://ebpf.io/) is a technology that allows custom programs to run in the Linux kernel. eBPF is used in various networking, security, and performance monitoring applications. [XDP](https://www.iovisor.org/technology/xdp) provides a framework for eBPF that enables high-performance programmable packet processing in the Linux kernel. XDP runs in the network driver, before the kernel builds a socket buffer for the packet. TCX is a second attach point, added in kernel 6.6. It runs later, on the socket buffer, and is available on every interface regardless of driver support. ## Data Plane Packet processing in Ella Core Ella Core's data plane uses eBPF to achieve high throughput and low latency. Key features include: - **Policy rules enforcement**: Evaluating ordered per-policy uplink and downlink rules to allow or deny traffic based on remote prefix, protocol, and port range. - **Encapsulation and decapsulation**: Managing GTP-U (GPRS Tunneling Protocol-User Plane) headers for data transmission. - **Rate limiting**: Enforcing Quality of Service (QoS) with QER (QoS Enforcement Rules). - **Flow reporting**: Recording per-flow traffic details including source, destination, protocol, port, and whether the flow was allowed or dropped. - **Usage reporting**: Aggregating per-subscriber byte counts for data usage tracking. - **Statistics collection**: Monitoring metrics such as packet counts, drops, and processing times. Packet processing in Ella Core with eBPF and XDP (Simplified to only show N3->N6). ### Routing Ella Core currently relies on the kernel to make routing decisions for incoming network packets. Kernel routes can be configured using the [Networking API](https://docs.ellanetworks.com/reference/api/networking/index.md) or the user interface. ### NAT Network Address Translation (NAT) lets subscribers use private addresses without an external router: uplink traffic leaves N6 sourced from Ella Core's own address, and the return traffic is translated back. The trade-off is reachability. A subscriber is reachable only through a flow it started itself, and traffic that cannot be translated is dropped rather than forwarded. To reach subscribers from the data network, turn NAT off and route the UE pool instead. See [Connectivity](https://docs.ellanetworks.com/reference/connectivity/#nat) for what NAT translates and what it drops. ### Performance Detailed performance results are available [here](https://docs.ellanetworks.com/reference/performance/index.md). ### Attach modes The data plane can attach at either of two kernel hooks, and the choice decides how much of the kernel's own processing has already happened when it sees a packet. - **xdp-native** runs in the network driver, before the kernel builds a socket buffer. Nothing has been merged, offloaded or annotated yet, so the data plane sees exactly what arrived on the wire. That is why it is the fastest option, and why it needs a driver that supports the hook. - **tcx** runs on the socket buffer, after the kernel's receive path. It is available on every interface, including the veth pairs used in containers and [co-hosted deployments](https://docs.ellanetworks.com/how_to/co_host_with_ocudu/index.md), which is what makes it the option that always works. - **xdp-generic** presents the XDP interface but runs late in the receive path, like TCX. It should only be used for testing and prototyping. ### Merged packets Both hooks that run on the socket buffer can be handed one holding several packets merged together — merged by the kernel's receive path, or handed over already merged by a veth or virtio peer that offloads segmentation. Neither encapsulation nor decapsulation can produce valid GTP-U from that. Encapsulation writes one tunnel header for the whole buffer, and when the kernel splits it back into wire-sized packets it copies that header onto each one unchanged: every packet then claims the merged buffer's GTP-U payload length rather than its own, and with an IPv6 outer header its checksum as well. Decapsulation strips only the first packet's outer headers and leaves the rest in the payload. TCX can see that the buffer is merged and drops it. Generic XDP cannot: a merged buffer over the MTU is answered with a spurious ICMP "fragmentation needed" and its payload is destroyed, and one under the MTU leaves as the malformed GTP-U above. Either way the remedy is to [disable merged packets](https://docs.ellanetworks.com/how_to/disable_merged_packets/index.md) on N3 and N6. ### Checksum offload on veth pairs An application transmitting over a veth leaves the transport checksum for the egress NIC to complete, recording where to write it in the packet's metadata. In `xdp-generic` mode the kernel does not update that metadata when the data plane removes the GTP-U header, so the checksum is later written at the stale offset, corrupting the decapsulated packet at a position that depends on the header removed. Nothing detects it: neither the data plane counters nor a capture on the host. Disabling TX checksum offload on both ends of the pair (`ethtool -K tx off`) forces the checksum to be completed before the packet reaches the data plane. `xdp-native` forwards redirected frames as raw packets, which carry no such metadata. TCX drops the request when it removes the header, because the kernel invalidates it once the checksum's start offset falls outside the packet — that covers decapsulation only, not a frame the data plane encapsulates. ### XDP redirect on veth pairs When Ella Core's N3 interface is a veth pair, the data plane forwards downlink packets from N6 to N3 with `bpf_redirect()`. In `xdp-native` mode the veth driver delivers redirected frames through the native path only when the receiving peer also has an XDP program attached; without one, the frames are dropped. Attaching a minimal `XDP_PASS` program to the peer satisfies that requirement — see [Use native XDP with veth interfaces](https://docs.ellanetworks.com/how_to/native_xdp_veth/index.md). ### IPv6 GTP-U transport Ella Core supports GTP-U encapsulation with either an IPv4 or IPv6 outer header on the N3 / S1-U interface. The inner UE payload can be IPv4 or IPv6, independent of the transport address family. The chosen transport address family depends on how the N3 / S1-U interface is configured, and what the radio advertises. If both sides are dual-stack, Ella Core prefers IPv6. **GTP echo:** Echo Request/Response messages are handled for both IPv4 and IPv6 transport, as required for GTP-U path management.