Magma eBPF - Deployment Guide
This guide deploys a complete Magma 5G core with our eBPF GTP-U user-plane datapath:
- Magma Orchestrator (orc8r) - the cloud controller + NMS, deployed with
the
magma-orc8rAnsible deployer. - Magma Access Gateway (AGW) - the edge node, deployed as Docker containers,
then switched from the kernel
gtp0datapath to three eBPF/TC programs that do GTP-U encap/decap while OVS keeps flow management (QFI ridesskb->mark→ OVSpkt_mark).
Part 1 - Deploy Magma Orchestrator (orc8r)
Code: https://github.com/TOSSI-Foundation/magma-orc8r (the magma-deployer
Ansible/RKE+Helm tooling)
Upstream reference: https://magma.github.io/magma/docs/next/orc8r/deploy_using_ansible
1.0 Prerequisites
- A clean Ubuntu host (VM or bare metal) with internet access, run as
root. - Enough resources for a single-node Kubernetes + orc8r (≈ 4 vCPU / 8–16 GiB / 40 GiB).
1.1 Quick install
sudo bash -c "$(curl -sL https://github.com/TOSSI-Foundation/magma-orc8r/raw/main/deploy-orc8r.sh)"
The script will prompt for (press Enter to accept the defaults):
| Prompt | Default |
|---|---|
| Orchestrator domain name | magma.local |
| NMS organization (subdomain) | magma-test |
| NMS email ID | admin |
| NMS password | admin |
It then:
1. installs yq + ansible,
2. creates a magma system user with passwordless sudo and an SSH key,
3. clones the deployer and runs ansible-playbook deploy-orc8r.yml, which brings up
the full stack as roles: prerequisites → docker → kubernetes → openebs →
metallb → postgresql → secrets → orc8r → prometheus → haproxy → elasticsearch →
fluentd → dns.
1.2 Switch to the magma user (after the deploy finishes)
sudo su - magma
1.3 Set up the NMS login (once all pods are Ready)
cd ~/magma-deployer
ansible-playbook config-orc8r.yml
Check the pods first if you want to be sure:
kubectl get pods -A # wait until orc8r-* and nms-* are Running/Ready
1.4 Grab the root CA (needed by the AGW in Part 2)
cat ~/magma-deployer/secrets/rootCA.pem
Save this file - the Access Gateway must trust it to register with the
controller. You will copy it to the AGW at /var/opt/magma/certs/rootCA.pem
(see Part 2.2).
The NMS is then reachable at https://<nms-org>.<orc8r-domain> (e.g.
https://magma-test.magma.local) with the email/password you set.
Part 2 - Deploy the Magma AGW with the eBPF GTP-U datapath
Code: https://github.com/TOSSI-Foundation/magma/tree/ebpf - all of our work
lives under lte/gateway/ebpf/.
2.1 Our work - what the eBPF datapath is (code detail)
We replace the kernel gtp0 vport (out-of-tree openvswitch.ko GTP encap/decap)
with three TC/eBPF programs attached with clsact, sharing three pinned maps.
OVS keeps doing flow management; QFI is lifted off the wire into skb->mark,
which unmodified OVS reads back as pkt_mark. ~1,400 LoC, ~73% eBPF C.
Folder layout (lte/gateway/ebpf/)
bpf/ gtp_decap.c · gtp_encap.c · gtp_mark.c · gtp_maps.h · Makefile the TC programs + map ABI
src/ tc_loader.c libbpf loader (attach + pin)
python/ ebpf_control.py · ebpf_session_adapter.py map primitives + reconciling adapter
deploy/ install-ebpf.sh · setup/teardown-ebpf-gtp.sh · turnkey host install
magma-ebpf-gtp.service · magma-ebpf-adapter.service systemd units
ansible/ roles/ebpf/* · playbooks/deploy_ebpf.yml production Ansible role
tests/ test_phase2_protocol.py · test_phase2_loader.py · e2e_validate.sh unit / component / system
tools/ ebpf-status.sh · ebpf-demo.sh inspection + live demo
The three eBPF programs
| Program | Attach point | Direction | What it does |
|---|---|---|---|
gtp_decap.c (gtp_decap) |
N3/S1-U eth1 ingress |
Uplink | Parses the GTP-U header, validates TEID against the per-UE session, writes QFI into skb->mark, strips the outer IP/UDP/GTP, and bpf_redirects the inner packet to gtp_veth0 (into OVS). |
gtp_mark.c (gtp_veth0_mark) |
gtp_veth0 ingress |
Uplink (seam) | Restores/normalizes skb->mark so OVS classifies the flow on pkt_mark (= QFI). This is the QFI seam - no GTP match field, no OVS fork. |
gtp_encap.c (gtp_encap) |
gtp_veth1 ingress |
Downlink | Looks up the UE session, builds the GTP-U header (DL TEID + QFI + PDU-session container), bpf_adjust_head to push the outer headers, and sends it out N3 to the gNB. Includes the MTU/DF black-hole guard. |
Pinned maps (/sys/fs/bpf, shared by all three programs)
ue_session_map- key = UE IP; value ={ul_teid, dl_teid, qfi, session_flags, ul_packets, ul_bytes, dl_packets, dl_bytes}. The live per-subscriber forwarding + accounting state.config_map- datapath config by index:S1U_IFINDEX, SGI_IFINDEX, OVS_IFINDEX, DEBUG_LEVEL, SGI_IP, VETH_IFINDEX, LINK_MTU.stats_map- global counters:UL/DL_PKTS,UL/DL_BYTES,GTP_DECAP_OK,GTP_ENCAP_OK,SESSION_MISS,TEID_MISMATCH,PKT_FORWARDED/DROPPED, etc.
Loader & control plane
src/tc_loader.c- a libbpf loader that compiles-in via CO-RE, attaches the three programs withclsact, and pins the maps. Runs asmagma-ebpf-gtp.service.python/ebpf_session_adapter.py- an event-driven adapter that mirrors pipelined's per-sessiongtp_br0OVS flows intoue_session_mapwhenever they change. Magma's signaling stack and container images are never modified - sessions programmed by an external gNB/simulator through the stock AGW are auto-reconciled into eBPF. Runs asmagma-ebpf-adapter.service.python/ebpf_control.py- thin map read/write primitives overbpftool.
Data path summary
Uplink: gNB ──GTP-U──▶ eth1 ─[gtp_decap: strip GTP, QFI→skb->mark]─▶ gtp_veth0
─[gtp_mark]─▶ OVS gtp_br0 (match pkt_mark=QFI) ──▶ SGi ──▶ internet
Downlink: internet ──▶ SGi ──▶ OVS gtp_br0 ──▶ gtp_veth1 ─[gtp_encap: build GTP-U, DL TEID+QFI]─▶ eth1 ──▶ gNB
The SCTP/N2 control plane on eth1 is untouched; only the GTP-U user plane moves
into eBPF.
2.2 Prerequisites on the AGW host
- A clean Ubuntu host with two NICs:
eth0(mgmt/SGi) andeth1(N3/S1-U to the gNB). - Copy the orc8r root CA from Part 1.4 onto the AGW:
sudo mkdir -p /var/opt/magma/certs
sudo nano /var/opt/magma/certs/rootCA.pem # paste the rootCA.pem contents
2.3 Install the Docker AGW (with our ebpf-branch code)
Run the installer, pointing it at the TOSSI ebpf branch so /opt/magma contains
our lte/gateway/ebpf/ folder:
sudo GIT_URL=https://github.com/TOSSI-Foundation/magma.git \
MAGMA_VERSION=ebpf \
EBPF_DATAPATH=true \
bash -c "$(curl -sL https://raw.githubusercontent.com/TOSSI-Foundation/magma/refs/heads/ebpf/lte/gateway/deploy/agw_install_docker.sh)" agw
What this script does / what you'll see when it finishes:
1. Verifies it's root on Ubuntu and that rootCA.pem is present.
2. Configures DNS, holds the running kernel, disables unattended-upgrades.
3. Installs Docker CE + the compose plugin; creates the ubuntu user (sudo + docker).
4. Installs ansible==5.0.1, clones the repo to /opt/magma, checks out the branch.
5. Renames NICs to eth0/eth1 and applies netplan.
6. Runs ansible-playbook magma_docker.yml (tag agwc) which installs OVS + the
host dependencies and stages the containers under /var/opt/magma/docker/.
7. Prints "Reboot this VM to apply kernel settings." → reboot.
2.4 Bring up the AGW containers
The Ansible run stages and starts the stack; to (re)bring it up manually:
cd /var/opt/magma/docker
sudo docker compose --compatibility up -d
sudo docker ps # all components should be Up
The AGW components (containers): magmad, oai_mme, sctpd, pipelined, sessiond,
mobilityd, subscriberdb, policydb, redis, directoryd, enodebd, state, control_proxy,
eventd, … (22 services).
2.5 Deploy / confirm the eBPF datapath
If you did not use EBPF_DATAPATH=true, deploy the host-side datapath now
(idempotent, safe to re-run):
sudo /opt/magma/lte/gateway/ebpf/deploy/install-ebpf.sh # auto-detects N3 (eth1)
What it does:
- builds the three eBPF objects + tc_loader (pulls libbpf 1.3 + bpftool if needed),
- writes /etc/magma/ebpf.env, installs the two systemd units, and
systemctl enable --now magma-ebpf-gtp.service magma-ebpf-adapter.service,
- removes the kernel gtp0 vport (ovs-vsctl del-port gtp_br0 gtp0) - eBPF now owns GTP-U,
- attaches gtp_decap@eth1, gtp_veth0_mark@gtp_veth0, gtp_encap@gtp_veth1 and pins the maps.
When it's up, any external gNB/simulator that attaches a UE through the stock AGW is auto-programmed into eBPF (via the adapter) - no per-UE manual steps.
Part 3 - Verify & inspect (manual check commands)
3.1 Containers & component logs
sudo docker ps # every AGW component should be Up
sudo docker logs -f --tail 200 oai_mme # MME signaling (S1AP/NGAP/NAS) - UE attach/register
sudo docker logs -f --tail 200 sessiond # session/PDU + policy
sudo docker logs -f --tail 200 pipelined # OVS / datapath programming
sudo docker logs --tail 200 magmad # agent + orc8r check-in
# generic: sudo docker logs -f --tail 200 <component>
MME log (inside the container, if you prefer the file):
sudo docker exec oai_mme tail -f /var/log/mme.log 2>/dev/null \
|| sudo docker logs -f oai_mme
Confirm orc8r check-in (gateway shows "connected" in the NMS):
sudo docker logs magmad 2>&1 | grep -iE 'checkin|Successfully sent'
3.2 One-shot eBPF status (our tool)
sudo /usr/local/bin/ebpf-status.sh
It prints: the two services, the three TC programs (with bytecode tags), the
pinned maps, the decoded config_map, nonzero stats_map counters, the live
per-UE sessions (TEID/QFI + UL/DL counters), and the OVS QFI classifier
flows (cookie=0xeb…).
3.3 The eBPF checks, by hand
# (a) the three TC programs are attached
sudo tc filter show dev eth1 ingress | grep gtp_decap
sudo tc filter show dev gtp_veth0 ingress | grep gtp_veth0_mark
sudo tc filter show dev gtp_veth1 ingress | grep gtp_encap
sudo bpftool prog show | grep -E 'gtp_(decap|encap|veth0_mark)'
# (b) maps pinned + live per-UE session (TEID/QFI/counters)
sudo bpftool map show pinned /sys/fs/bpf/ue_session_map
sudo bpftool map dump pinned /sys/fs/bpf/ue_session_map
sudo bpftool map dump pinned /sys/fs/bpf/stats_map
# (c) the QFI seam - stock OVS classifies UL on the eBPF-written mark (= QFI)
sudo ovs-ofctl dump-flows gtp_br0 | grep 'cookie=0xeb'
# the legacy kernel-GTP flows now carry 0 packets (forwarding moved to eBPF):
sudo ovs-ofctl dump-flows gtp_br0 | grep -E 'NXM_NX_TUN_ID|in_port=32768'
# (d) live per-packet trace (decap parses UL, encap builds DL) - needs DEBUG_LEVEL>0
sudo timeout 30 cat /sys/kernel/debug/tracing/trace_pipe | grep -E 'GTP|ENCAP|QFI|Session'
# (e) full guided terminal demo (7 shots)
sudo /usr/local/bin/ebpf-demo.sh # AUTO=1 to skip pauses; TRACE_SECS=30
Expected proof: the OVS UL classifier flow's n_packets equals the UE's
ul_packets in ue_session_map → QFI is riding skb->mark/pkt_mark with no
switch fork and no GTP match field; the legacy NXM_NX_TUN_ID flows stay at
n_packets=0 because GTP forwarding now lives entirely in eBPF.
Appendix - directory & path reference
| Path | What |
|---|---|
/opt/magma |
the cloned repo (ebpf branch) - source of lte/gateway/ebpf/ |
/var/opt/magma/docker/ |
AGW compose dir (.env, docker-compose.yaml) - docker compose up -d here |
/var/opt/magma/certs/rootCA.pem |
orc8r root CA the AGW must trust |
/var/opt/magma/ebpf/ |
built eBPF objects + tc_loader + adapter |
/etc/magma/ebpf.env |
datapath config (S1U_IF, OVS_BR, OVS_VETH, …) |
/sys/fs/bpf/{ue_session_map,config_map,stats_map} |
pinned maps |
/usr/local/bin/ebpf-status.sh, ebpf-demo.sh |
inspection + demo tools |