Open5GS set-up from modified source code to custom deploy
Setting up private mobile network with modified Open5GS. Add custom solutions to the open-source implementation and deploy with debian
Open5GS set-up from modified source code to custom deploy
Good day for all Open5GS enthusiasts out there, today I took the legacy of our older article to expand it to custom software solutions for 5G SA packet core.
I will provide step-by-step guide to:
- Fork and modify the source code from Open5GS github
- Provide basic code overview and modification approaches
- Build the executable files
- Run the core manually
Note: I am not a part of Open5GS team and talking only from my limited experience. I tried my best not to mislead anybody but take it with a grain of salt. If something is wrong — please let me know
Note: you must share all distributed modifications of Open5GS under its AGPL 3.0 license
Open5GS code
At the very beginning of our journey you should fork the original Open5GS repository. I assume it can be done without specific instructions.
First of all: what Open5GS actually is and how is it built?
It is a meson-based project written in pure C, some 5 million lines of old-school code. The project is built to single-file executables (per network function, NF) while each of them requires dynamically linked libraries.
Let’s overview the structure:
- configs/ contains configuration for Open5GS itself, freeDiameter (open-source implementation of Diameter protocol) and systemd services
- debian/ contains relatively well-structured debian package build configurations
- docker/ contains basic docker set-up for Open5GS but it will not be discussed in this article
- docs/ is a bundle of insanely limited documentation. Better use the official website
- lib/ stores actual source code for all its dependencies and custom common modules
- misc/ contains some helper scripts
- src/ is the heart of the project containing most its codebase
- subprojects/ stores entire freeDiameter and Prometheus client projects
- tests/ defines all the tests (Unit, integration etc)
- vagrant/ is a simple vagrant config
- webui/ contains primitive Web UI to the HSS DB. Don’t show this one to FE developers
It is a huge project and we don’t take too much here. Let’s just play with src, lib, config and debian. Let’s start with example code modifications and build-run the project later.
Example code modification
Note: I am no specialist with Open5GS codebase and we will discuss some examples only.
First of all, we need to set some simple task:
We need to send information about all incoming UE registration requests to monitoring system. POC would be: sending enb-id of any incoming UE registration request to simple TCP socket on localhost.
Chosen approach:
- Create custom primitive library (only standard C library) to handle monitoring senders
- Insert the senders to relatable files with implementation of expected processes
- Build and deploy using debian packages
Custom library
New library would be created under /lib/example directory with the following files: 1. example.h:
#ifndef EXAMPLE_H
#define EXAMPLE_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
// Global configuration for the custom TCP socket
#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 12345
// Struct to work with original Open5GS plmn_id structure
typedef struct monitoring_plmn_id_s {
uint8_t mcc1 : 4;
uint8_t mcc2 : 4;
uint8_t mcc3 : 4;
uint8_t mnc1 : 4;
uint8_t mnc2 : 4;
uint8_t mnc3 : 4;
} __attribute__((packed)) monitoring_plmn_id_t;
// Monitoring sender to be injected in original process flow in amf
void example_function(const void* plmn_id_input);
// Custom sender to the TCP server
void send_data_to_server(const char *data, int len);
#endif //EXAMPLE_H
2. example.c:
#include "example.h"
// Injected function to process the report
void example_function(const void* plmn_id_input) {
// Convert operational plmn_id struct to human-readable string
char plmn_string[7];
const monitoring_plmn_id_t* plmn_id = (monitoring_plmn_id_t*)plmn_id_input;
plmn_string[0] = '0' + plmn_id->mcc1;
plmn_string[1] = '0' + plmn_id->mcc2;
plmn_string[2] = '0' + plmn_id->mcc3;
plmn_string[3] = plmn_id->mnc1 == 15 ? '0' + 0 : '0' + plmn_id->mnc1;
plmn_string[4] = '0' + plmn_id->mnc2;
plmn_string[5] = '0' + plmn_id->mnc3;
plmn_string[6] = '\0';
// handle 5-or-6 digits PLMN conversion
if (plmn_id->mnc1 == 15) {
plmn_string[3] = '0' + 0;
}
else{
plmn_string[3] = '0' + plmn_id->mnc2;
}
send_data_to_server(plmn_string, 7);
}
3. socket.c:
#include "example.h"
// Custom sender to the TCP server
void send_data_to_server(const char *data, int len) {
// Typical C-like socket
int sockfd;
struct sockaddr_in server_addr;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
return;
}
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0) {
close(sockfd);
return;
}
// Continue regular flow without successful monitoring execution
if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("Connection failed");
close(sockfd);
return;
}
if (send(sockfd, data, len, 0) < 0) {
printf("Error sending data\n");
} else {
printf("Sent: %s\n", data);
}
close(sockfd);
}
4. meson.build:
Declares additional dynamically linked library (.so file) under meson project structure
example_sources = files('''
example.h
socket.c
example.c
'''.split())
libexample = library('example',
sources : example_sources,
install : true)
libexample_dep = declare_dependency(
link_with : libexample)
We also need to include it to lib/meson.build:
--- open5gs/lib/meson.build 2025-03-20 18:41:55.107597363 +0100
+++ test5gs/lib/meson.build 2025-03-10 19:03:14.298056874 +0100
@@ -34,3 +34,4 @@
subdir('gtp')
subdir('pfcp')
subdir('sbi')
+subdir('example')
And update /debian/open5gs-common.install to bundle the compiler library with the debian package:
--- open5gs/debian/open5gs-common.install 2025-03-20 18:41:54.942601471 +0100
+++ test5gs/debian/open5gs-common.install 2025-03-10 20:38:01.928156741 +0100
@@ -1,5 +1,6 @@
usr/lib/*/libogs*.so*
usr/lib/*/libfd*.so*
+usr/lib/*/libexample.so
usr/lib/*/freeDiameter/*.fdx
usr/lib/*/lib*prom*.so*
configs/open5gs/tls/ca.crt /etc/open5gs/tls
Our custom library is ready to be called from any place of the project!
AMF monitoring
Okay we have the sender library but what’s next? How do we find where we use it?
In such a large projects it’s quite a challenge to find anything with rare comments and no official documentation, huh? Not that hard actually.
Here is how we can use OpenAI’s ChatGPT 4o to locate the exact function. It is not a panacea however, the last response made up all the Plmn-ID Handling part, but the main thing — it successfully located the exact function for us. Keep in mind that most work is still to be done by our natural stupidity while artificial intelligence can only be a useful tool ;)
With some extra search we can locate that plmn_id is located under amf_ue->nt_tai.plmn_id structure. The shortest path to do so is to run the AMF with debugger (CLion from JetBrains supports meson out-of-the-box) and investigate all the structs passed to the target function. However, manual cd src/amf && grep -r ‘amf_ue_t’ works as well. Up to you!
Now we know everything to inject our monitoring function:
--- open5gs/src/amf/gmm-handler.c 2025-03-20 18:41:55.382590522 +0100
+++ test5gs/src/amf/gmm-handler.c 2025-03-20 19:02:35.331492050 +0100
@@ -24,6 +24,7 @@
#include "sbi-path.h"
#include "gmm-handler.h"
+#include "example/example.h"
#undef OGS_LOG_DOMAIN
#define OGS_LOG_DOMAIN __gmm_log_domain
@@ -41,6 +42,9 @@
int served_tai_index = 0;
uint8_t gmm_cause;
+ // Include our custom function
+ example_function(&amf_ue->nr_tai.plmn_id);
+
ran_ue_t *ran_ue = NULL;
ogs_nas_5gs_registration_type_t *registration_type = NULL;
ogs_nas_5gs_mobile_identity_t *mobile_identity = NULL;
We also would need to link our custom library to the src/amf/meson.build:
--- open5gs/src/amf/meson.build 2025-03-20 18:41:55.383590497 +0100
+++ test5gs/src/amf/meson.build 2025-03-10 19:05:08.246642186 +0100
@@ -78,7 +78,8 @@
libsctp_dep,
libngap_dep,
libnas_5gs_dep,
- libsbi_dep])
+ libsbi_dep,
+ libexample_dep])
amf_sources = files('''
app.c
monitoring@monit
Build and run
Now, regardless whether modification were made or not, we can build our Open5GS project from source to debian packages. But which packages do we even need? What do these debian configurations mean?
Let’s break down the structure:

- changelog — typical CI/CD generated changelog, don’t touch this one
- compat specifies debhelper compatability version (here is 11)
- control provides descriptive information along with each package dependencies
- copyright — license information
- *.install — which files to be copied to the final packages
- *.postint/.postrm* — installation/removal scripts, mostly for custom user creation and systemd set-up
- rules — main execution script to define custom build-time instructions
We don’t really need to change anything there but for further development with debian packages as deployment you would have to. It’s a pretty cost-effective approach compared to docker but it’s a whole another discussion.
I am using this command to generate debian packages
DEB_BUILD_OPTIONS="noddebs" dpkg-buildpackage -b -us -uc
# Do not copy this as a command ;)
DEB_BUILD_OPTIONS="noddebs" # Skip debug symbol packages
dpkg-buildpackage # Build Debian package
-b # Build binary packages only
-us # Skip signing source package
-uc # Skip signing .changes file
This would generate a bunch of debian packages for each NF +some Open5GS-specific utilities:

Install them one by one, 5G SA Core only:
sudo dpkg -i open5gs-common_2.7.2_amd64.deb
sudo dpkg -i open5gs-scp_2.7.2_amd64.deb
sudo dpkg -i open5gs-nrf_2.7.2_amd64.deb
sudo dpkg -i open5gs-amf_2.7.2_amd64.deb
sudo dpkg -i open5gs-smf_2.7.2_amd64.deb
sudo dpkg -i open5gs-udr_2.7.2_amd64.deb
sudo dpkg -i open5gs-udm_2.7.2_amd64.deb
sudo dpkg -i open5gs-ausf_2.7.2_amd64.deb
sudo dpkg -i open5gs-pcf_2.7.2_amd64.deb
sudo dpkg -i open5gs-upf_2.7.2_amd64.deb
This would start all the services just as it would be done with apt manager with the Quickstart guide. Just some extra steps ;)
This implies that minimal Open5GS set-up for 5G SA is: open5gs-common (library) package, SCP + NRF, SMF + PCF + UPF, UDR + UDM, AMF + AUSF. While SEPP and NSSF are optional.
Testing
We don’t cover configuration for the provided NFs or UERANSIM, for such details please refer my older guide about Roaming which would be enough to configure a subset of NFs discussed there.
gNB successful set-up:

UE succesfull registration:

TCP socket received the report from our custom library:

Note: the socket is closed immediately after which is not the best practice but assuming solely demonstrating purposes it would be enough
Summary
We have discussed some practical approach to set up Open5GS project from modified source code along with basic C-like techniques. We have also looked at deploy with debian packages which is used within our 5G Lab. This would be enough to start with adding your custom solutions for a private 5G SA mobile network with Open5GS project.
Follow up articles:
- Open5GS mTLS configuration
- TLS-Resilient Monitoring for Open5GS in Secure and Low-Footprint 5G Deployments (main)

Italian brainrot is trending now idk
메타데이터
- post_id
- e6d244b98f4b
- slug
- open5gs-set-up-from-modified-source-code-to-custom-deploy-e6d244b98f4b
- url
- https://medium.com/networkers-fiit-stu/open5gs-set-up-from-modified-source-code-to-custom-deploy-e6d244b98f4b
- canonical_url
- https://medium.com/networkers-fiit-stu/open5gs-set-up-from-modified-source-code-to-custom-deploy-e6d244b98f4b
- author_url
- https://medium.com/@vidime.sa.buduci.rok
- status
- ok
- fetched_at
- 2026-06-24 11:06:28