5G NF Platform: Development Plan¶
Standalone specification for the 5g-nf-platform repository. Copy this document to the
root of that repository as CLAUDE.md or SPEC.md before starting development.
What this repository is¶
A container image factory for 5G Network Functions. It pulls source from one or more
upstream projects (Open5GS, free5GC, custom), applies research patches, builds one Docker
image per NF, and publishes versioned images to a container registry. It does not deploy
anything. Deployment is handled by consumers (e.g., kelt).
What this repository is not¶
- A fork of Open5GS or any other upstream project.
- A deployment tool.
- A testbed or simulation environment.
- A monorepo containing all NF source code.
Upstream source is pulled at build time via git clone at a pinned tag. It is not vendored
into this repository.
Build order: do these in sequence¶
Phase 1: Scaffold and first working image¶
- Create the directory structure (see below).
- Write
Dockerfilefor one NF only: SMF. Reason: SMF is the first NF that needs a patch (session-infoendpoint). Getting one NF building end-to-end validates the full pipeline before scaling to the others. - Write the
versions.jsonschema (see below). - Write GitHub Actions workflow
build.ymlthat builds and pushes SMF only. - Verify:
docker pull ghcr.io/<owner>/5g-nf-platform/smf:<tag>works.
Phase 2: SMF session-info patch¶
- Clone Open5GS at the pinned tag locally.
- Add the HTTP handler to
src/smf/app.c(see patch spec below). - Export as
nfs/smf/patches/0001-session-info-endpoint.patch. - Update SMF Dockerfile to apply the patch before building.
- Verify:
curl http://127.0.0.1:9090/session-infoinside SMF container returns JSON.
Phase 3: Remaining Open5GS NFs¶
Add one Dockerfile per NF: AMF, UPF, UDM, AUSF, UDR, NRF, PCF, BSF, NSSF. No patches on
these initially. Extend build.yml to build all of them. Update versions.json.
Phase 4: CI automation for upstream updates¶
- Add
check-upstream.ymlscheduled workflow (daily). - Workflow: query GitHub releases API for open5gs/open5gs, compare with pinned tag in
upstream-versions.json, open a PR if newer tag exists. - PR updates the pinned tag per NF, increments
patch-revto 0 if no patches change.
Phase 5: First experimental NF¶
Choose one: NEF from free5GC or a custom LMF stub. This phase validates the experimental NF path end-to-end including NRF registration compatibility with Open5GS NRF.
Directory structure¶
5g-nf-platform/
├── nfs/
│ ├── amf/
│ │ ├── patches/ # empty if no patches
│ │ └── Dockerfile
│ ├── smf/
│ │ ├── patches/
│ │ │ └── 0001-session-info-endpoint.patch
│ │ └── Dockerfile
│ ├── upf/
│ ├── udm/
│ ├── ausf/
│ ├── udr/
│ ├── nrf/
│ ├── pcf/
│ ├── bsf/
│ ├── nssf/
│ └── experimental/
│ ├── nef/
│ └── lmf/
├── .github/
│ └── workflows/
│ ├── build.yml
│ └── check-upstream.yml
├── upstream-versions.json # pinned upstream tag per NF
├── versions.json # published image tag per NF (updated by CI)
├── SPEC.md # this document
└── README.md
Dockerfile pattern (per NF)¶
Each Dockerfile follows the same pattern: clone upstream at pinned tag, apply patches, build, produce a minimal runtime image.
# Baseline tested with v2.7.5 (version from original manual build).
# v2.7.5 is the pinned default; update upstream-versions.json to change it.
ARG UPSTREAM_TAG=v2.7.5
ARG NF_NAME=smf
FROM ubuntu:22.04 AS builder
RUN apt-get update && apt-get install -y \
git cmake gcc g++ libsctp-dev libgnutls28-dev \
libgcrypt-dev libssl-dev libidn11-dev libmongoc-dev \
libbson-dev libmicrohttpd-dev libyaml-dev \
python3-pip meson ninja-build pkg-config
RUN pip3 install meson
ARG UPSTREAM_TAG
RUN git clone --depth 1 --branch ${UPSTREAM_TAG} \
https://github.com/open5gs/open5gs /src/open5gs
# WebUI (Node.js) is intentionally excluded. Subscriber management and
# monitoring are handled by the kelt dashboard.
COPY patches/ /patches/
RUN cd /src/open5gs && \
for p in $(ls /patches/*.patch 2>/dev/null | sort); do \
git apply "$p"; \
done
ARG NF_NAME
RUN cd /src/open5gs && \
meson build --prefix=/install && \
ninja -C build install
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y \
libsctp1 libgnutls30 libgcrypt20 libssl3 \
libidn11 libmongoc-1.0-0 libyaml-0-2 \
&& rm -rf /var/lib/apt/lists/*
ARG NF_NAME
COPY --from=builder /install/bin/open5gs-${NF_NAME}d /usr/local/bin/
COPY --from=builder /install/lib/ /usr/local/lib/
RUN ldconfig
# All Open5GS NFs expose a management HTTP server on port 9090.
# The healthcheck polls it; a 200 response means the NF is operational.
HEALTHCHECK --interval=10s --timeout=5s --start-period=10s --retries=3 \
CMD curl -sf http://127.0.0.1:9090/ || exit 1
ENTRYPOINT ["/bin/sh"]
Note: the testbed's ansible phase uses an init script as the actual entrypoint. The Dockerfile entrypoint is intentionally minimal.
versions.json schema¶
{
"_comment": "Canonical image tags. Updated by CI on each successful build.",
"amf": "ghcr.io/<owner>/5g-nf-platform/amf:2.7.2-p0",
"smf": "ghcr.io/<owner>/5g-nf-platform/smf:2.7.2-p3",
"upf": "ghcr.io/<owner>/5g-nf-platform/upf:2.7.2-p0",
"udm": "ghcr.io/<owner>/5g-nf-platform/udm:2.7.2-p0",
"ausf": "ghcr.io/<owner>/5g-nf-platform/ausf:2.7.2-p0",
"udr": "ghcr.io/<owner>/5g-nf-platform/udr:2.7.2-p0",
"nrf": "ghcr.io/<owner>/5g-nf-platform/nrf:2.7.2-p0",
"pcf": "ghcr.io/<owner>/5g-nf-platform/pcf:2.7.2-p0",
"bsf": "ghcr.io/<owner>/5g-nf-platform/bsf:2.7.2-p0",
"nssf": "ghcr.io/<owner>/5g-nf-platform/nssf:2.7.2-p0"
}
Tag format: <upstream-version>-p<patch-rev>. Patch revision increments per patch added
or changed. p0 means upstream only, no patches.
upstream-versions.json schema¶
{
"open5gs": "v2.7.5",
"free5gc": "v3.4.1"
}
This file is the single source of truth for which upstream tag each build uses. CI reads it. PRs that bump an upstream tag update only this file.
Versioning rules¶
- Never change the upstream tag without bumping
upstream-versions.jsonvia PR. - Never change a patch without incrementing
patch-revin the image tag. versions.jsonis written only by CI, never manually.- A tag once pushed to the registry is immutable. Do not retag.
SMF session-info patch specification¶
File to modify: src/smf/app.c (or wherever the Open5GS management HTTP server
registers routes, confirm with grep -r "ogs_app_management" src/smf/).
Endpoint: GET /session-info
Response body:
{
"sessions": [
{
"imsi": "001010123456786",
"dnn": "internet",
"ipv4": "10.45.0.6",
"ipv6": "",
"snssai": { "sst": 1, "sd": "000001" }
}
]
}
Implementation approach:
1. Register a new HTTP GET handler for /session-info in the SMF management server.
2. Iterate smf_sess_t linked list (the in-memory session table).
3. For each session: extract IMSI from sess->guti, DNN from sess->dnn, IPv4 from
sess->ue_ip.addr, S-NSSAI from sess->s_nssai.
4. Serialize to JSON using Open5GS's existing ogs_json_* helpers.
5. Return HTTP 200 with Content-Type: application/json.
The patch must not affect any 5G protocol behavior. It is read-only access to existing in-memory state.
Adding an experimental NF¶
Requirements checklist before starting¶
- [ ] Source is available under a compatible license (Apache 2.0 or similar preferred).
- [ ] NF registers with NRF using HTTP/JSON (SBI). Confirm schema compatibility with
Open5GS NRF (
/nnrf-nfm/v1/nf-instances). - [ ] NF does not require modifications to other NFs to function.
- [ ] A minimal smoke test exists: NF starts, registers with NRF, responds to a discovery query.
Steps¶
- Create
nfs/experimental/<nf-name>/withDockerfileandpatches/. - Add entry to
versions.jsonandupstream-versions.json. - Add build job to
build.yml. - Document interop status in
nfs/experimental/<nf-name>/README.md: which Open5GS version it was tested with, what works, what does not.
NRF compatibility¶
Open5GS NRF expects specific JSON fields in NF profile registration. If the experimental
NF uses a different schema (e.g., free5GC NRF schema), a compatibility shim may be needed.
Document any shim as a patch in nfs/experimental/<nf-name>/patches/.
Licensing¶
Each NF directory must contain a LICENSE or UPSTREAM_LICENSE file identifying the
license of the upstream source. This repository does not hold upstream source code; the
license applies to the patches and Dockerfiles, which are authored here.
| Open5GS-derived NFs | AGPLv3 — if this repository is public, patched source is already visible here. No additional action required. | | free5GC-derived NFs | Apache 2.0 — permissive, no copyleft obligations. | | Custom NFs | Owner's choice. |
Patches and Dockerfiles in this repository are authored works. Apply whatever license is appropriate for the project (AGPLv3 for consistency with Open5GS, or MIT/Apache for custom work). Choose before making the repository public.
Integration contract with kelt¶
This repository publishes versions.json to the default branch. The testbed reads it.
The testbed's ansible/group_vars/all.yml references image tags. When the testbed operator
wants to update an NF:
- Check
versions.jsonin this repository for the new tag. - Update the corresponding
nf_image_<name>variable inall.yml. - Run
ansible-playbook phases/05-5g-core/playbook.yml.
The testbed dashboard (future feature) automates steps 1 and 2 via the GitHub API and a confirmation prompt.
No other coupling exists between the two repositories. This repository does not know about the testbed's Kubernetes manifests, Vagrant setup, or networking. The testbed does not know about upstream sources, patches, or build tooling.
Open questions to resolve before Phase 5¶
- Which NRF, Open5GS or a standalone one, will serve as the registry for mixed-source deployments? Open5GS NRF has specific behavior that free5GC NFs may not match exactly.
- Is the experimental NEF needed as a full SBI NF (registered with NRF, consumed by AMF) or as a sidecar service (standalone HTTP API)? The answer changes the integration complexity significantly.
- For a custom LMF: does it need to integrate with an existing gNB LPP implementation, or is it a standalone positioning server? This determines the air interface requirements.