Skip to main content

Lab 3: Custom monitoring in RAN for further processing

This experiment is to deploy a 5G Standalone (SA) network using OpenAirInterface (OAI) RF Simulator gNB and OAI minimal 5GC. We also deploy FlexRIC as the Near-RT RIC. Finally a custom monitoring xApp with callbacks will be deployed on bare-metal connecting to the RIC.

open-ran-db.yaml
apiVersion: athena.trirematics.io/v1
kind: Network
metadata:
name: bubbleran
namespace: trirematics
spec:
slices:
- plmn: "00101"
dnn: "internet"
network-mode: "IPv4"
service-type: eMBB
differentiator: 0x000000
ipv4-range: "12.1.1.0/24"
ipv6-range: "2001:db8:1::/64"
access:
- name: oai-gnb
stack: 5g-sa
model: oai-ran/monolithic-gnb
identity:
an-id: 50
radio:
device: rf-sim
cells:
- band: n78
arfcn: 641280
bandwidth: 40MHz
subcarrier-spacing: 30kHz
tdd-config:
period: 5ms
dl-slots: 7
dl-symbols: 6
ul-slots: 2
ul-symbols: 4
controller: flexric.bubbleran
core-networks:
- minimal.bubbleran
core:
- name: minimal
stack: 5g-sa
model: oai-cn/minimal
identity:
region: 0
cn-group: 4
cn-id: 5
dns:
ipv4:
default: 8.8.8.8
secondary: 8.8.4.4
edge:
- name: flexric
stack: 5g-sa
model: mosaic5g/flexric
---
apiVersion: athena.trirematics.io/v1
kind: Terminal
metadata:
name: ue1
namespace: trirematics
spec:
vendor: oai
stack: 5g-sa
model: terminal/nr-rfsim
preferred-access: oai-gnb.bubbleran
target-cores:
- minimal.bubbleran
identity:
imsi: "001010000000001"
pin: "1234"
opc: "0xc42449363bbad02b66d16bc975d77cc1"
key: "0xfec86ba6eb707ed08905757b1bb44b8f"
sqn: "0xff9bb4000001"
slice:
dnn: "internet"
network-mode: "IPv4"
service-type: eMBB
differentiator: 0x000000
radio:
bands:
- n78
readiness-check:
method: ping
target: google-ip
interface-name: oaitun_ue0

---
apiVersion: athena.trirematics.io/v1
kind: Terminal
metadata:
name: ue2
namespace: trirematics
spec:
vendor: oai
stack: 5g-sa
model: terminal/nr-rfsim
preferred-access: oai-gnb.bubbleran
target-cores:
- minimal.bubbleran
identity:
imsi: "001010000000002"
pin: "1234"
opc: "0xc42449363bbad02b66d16bc975d77cc1"
key: "0xfec86ba6eb707ed08905757b1bb44b8f"
sqn: "0xff9bb4000001"
slice:
dnn: "internet"
network-mode: "IPv4"
service-type: eMBB
differentiator: 0x000000
radio:
bands:
- n78
readiness-check:
method: ping
target: google-ip
interface-name: oaitun_ue0

Deployment

Use the command brc install network open-ran-db.yaml to deploy the network. It should finish without errors and printout the three Kubernetes resource names that were created. Check for the status of the deployment using the command brc observe. Wait until all the Elements other than the UE are in the STATUS set to 1/1 Y state.

After deploying the network, before running the xApp, you should update the xApp configuration file with the IP of the deployed Near-RT RIC, the local source IP in the cluster subnet and the deployed database. To do so you may execute the update_conf.sh script which automatically extract the IPs from the cluster and updates the specified configuration file:

cd /path/to/xapp-source/conf_files
./update_conf.sh -c default.conf
tip

Once the network is deployed, traffic can be generated between the UEs and the network with the following commands:

brc test throughput ue1 dl gateway -- -t 600
brc test throughput ue2 dl gateway -- -t 600

With the brc test throughput tool the user can generate iperf traffic from the UPF (gateway) to the UE for downlink traffic (dl) or vice-versa for uplink (ul). For more details you may check the BubbleRAN CLI reference.

The script will prompt the user for selecting which Near-RT RIC the xApp should connect to (in case of multiple RICs and/or networks currently deployed). Since xapp_all_sm.conf also defines the database information, the script will prompt the user to specify which database in the deployed network to connect to.

After selecting a RIC and database to connect, an output as the following is obtained:

Select the RIC to configure (enter number):
1) flexric.flexric.bubbleran (10.244.2.142)
Choice [1-1]: 1
Selected RIC: flexric.flexric.bubbleran with IP 10.244.2.142
Local IP for xApp: 10.244.0.218
Select the Database to configure (enter number):
1) mysql-db.sdl.bubbleran (10.244.0.253)
Choice [1-1]: 1
Selected Database IP: 10.244.0.253
Config file 'xapp_all_sm.conf' updated successfully.

Once the configuration file has been updated accordingly, now the monitoring xApp can be run.

Custom monitoring xApp

lab3.c
/*
Copyright (C) 2021-2025 BubbleRAN SAS

External application
Last Changed: 2025-05-02
Project: MX-XAPP
Full License: https://bubbleran.com/resources/files/BubbleRAN_Licence-Agreement-1.3.pdf)
*/

#include "../include/src/xApp/e42_xapp_api.h"
#include "../include/src/sm/mac_sm/mac_sm_id.h"
#include "../include/src/sm/rlc_sm/rlc_sm_id.h"
#include "../include/src/sm/pdcp_sm/pdcp_sm_id.h"
#include "../include/src/sm/gtp_sm/gtp_sm_id.h"
#include "../include/src/sm/slice_sm/slice_sm_id.h"

#include <stdatomic.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>

static void cb_mac(sm_ag_if_rd_t const* rd, global_e2_node_id_t const* e2_node)
{
assert(rd != NULL);
assert(rd->type ==INDICATION_MSG_AGENT_IF_ANS_V0);
assert(rd->ind.type == MAC_STATS_V0);

mac_ind_msg_t const* msg = &rd->ind.mac.msg;

int64_t now = time_now_us_xapp_api();
printf("MAC ind_msg latency = %ld μs from E2-node type %d ID %d\n", now - msg->tstamp, e2_node->type, e2_node->nb_id.nb_id);
}

int main(int argc, char *argv[])
{
fr_args_t args = init_fr_args(argc, argv);

//Init the xApp
init_xapp_api(&args);
sleep(1);

e2_node_arr_xapp_t nodes = e2_nodes_xapp_api();

assert(nodes.len > 0);

global_e2_node_id_t* e2_node = &nodes.n[0].id;

printf("Connected E2 nodes = %d\n", nodes.len);

// MAC subscribe
sm_ans_xapp_t mac_hndl = report_sm_xapp_api(e2_node, SM_MAC_ID, "1_ms", cb_mac);
assert(mac_hndl.success == true);

// RLC subscribe
// Step 1
// Subscribe to RLC with a periodicity of 2 ms

// PDCP subscribe
// Step 2
// Subscribe to PDCP with a periodicity of 2 ms

// GTP subscribe
// Step 3
// Subscribe to GTP with a periodicity of 1000 ms

// SLICE subscribe
// Step 4
// Subscribe to SLICE with a periodicity of 100 ms

// Run for 10 seconds
sleep(10);

// Unsubscribe
rm_report_sm_xapp_api(mac_hndl.u.handle);
// Step 5
// Unsubscribe/Remove report from previous subscriptions

// Free the memory
// Step 6

return 0;
}

The xApp uses the MAC service model to subscribe to periodic statistics indications (every 1 ms) and measures the end-to-end latency of each indication.

The user can compile the new xApp doing in the terminal:

cd /path/to/xapp_sdk/build
cmake ..
make -j
sudo make install
ldconfig

Then in order to run it:

cd build
./labs/lab3 -c ../conf/default.conf

If ran successfully the output should be as follows:

09:56:34.440086 [INFO]:  conf_file.c:443 Config -c file to ../conf_files/default.conf
09:56:34.440153 [INFO]: e42_xapp_api.c:81 Git SHA1 fea7dffd78de11f0d97ef16a0fda2937cbab544e
09:56:34.440156 [INFO]: e42_xapp.c:191 NearRT-RIC Server IP Address = 10.244.2.155, PORT = 36422
09:56:34.440199 [INFO]: emb_sm_ag.c:93 Loaded SM(s) 10, custom SMs true
09:56:34.440211 [INFO]: emb_sm_ric.c:93 Loaded SM(s) 10, custom SMs true
09:56:34.440824 [INFO]: msg_handler_xapp.c:533 E42 SETUP-REQUEST tx
09:56:34.441092 [INFO]: msg_handler_xapp.c:374 E42 SETUP-RESPONSE rx xApp ID 7
09:56:34.441098 [INFO]: msg_handler_xapp.c:390 Connected E2 Node(s) 1
Connected E2 nodes = 1
09:56:35.441302 [INFO]: msg_handler_xapp.c:568 RIC_SUBSCRIPTION_REQUEST tx RAN_FUNC_ID 142 RIC_REQ_ID 1
09:56:35.442617 [INFO]: msg_handler_xapp.c:155 RIC_SUBSCRIPTION_RESPONSE rx RAN_FUNC_ID 142 RIC_REQ_ID 1
09:56:35.442661 [INFO]: msg_handler_xapp.c:568 RIC_SUBSCRIPTION_REQUEST tx RAN_FUNC_ID 143 RIC_REQ_ID 2
MAC ind_msg latency = 1289 μs from E2-node type 2 ID 50
09:56:35.443975 [INFO]: msg_handler_xapp.c:155 RIC_SUBSCRIPTION_RESPONSE rx RAN_FUNC_ID 143 RIC_REQ_ID 2
09:56:35.444001 [INFO]: msg_handler_xapp.c:568 RIC_SUBSCRIPTION_REQUEST tx RAN_FUNC_ID 144 RIC_REQ_ID 3
09:56:35.444130 [INFO]: msg_handler_xapp.c:155 RIC_SUBSCRIPTION_RESPONSE rx RAN_FUNC_ID 144 RIC_REQ_ID 3
09:56:35.444145 [INFO]: msg_handler_xapp.c:568 RIC_SUBSCRIPTION_REQUEST tx RAN_FUNC_ID 148 RIC_REQ_ID 4
09:56:35.444262 [INFO]: msg_handler_xapp.c:155 RIC_SUBSCRIPTION_RESPONSE rx RAN_FUNC_ID 148 RIC_REQ_ID 4
09:56:35.444276 [INFO]: msg_handler_xapp.c:568 RIC_SUBSCRIPTION_REQUEST tx RAN_FUNC_ID 145 RIC_REQ_ID 5
MAC ind_msg latency = 1242 μs from E2-node type 2 ID 50
09:56:35.445371 [INFO]: msg_handler_xapp.c:155 RIC_SUBSCRIPTION_RESPONSE rx RAN_FUNC_ID 145 RIC_REQ_ID 5
MAC ind_msg latency = 1224 μs from E2-node type 2 ID 50
MAC ind_msg latency = 1220 μs from E2-node type 2 ID 50
MAC ind_msg latency = 1223 μs from E2-node type 2 ID 50
MAC ind_msg latency = 1223 μs from E2-node type 2 ID 50

Extending the custom monitoring xApp

In order to extend our custom monitoring xApp to also use other service models, the following subscriptions can be added in the main.

For RLC:

  // RLC subscribe
sm_ans_xapp_t rlc_hndl = report_sm_xapp_api(e2_node, SM_RLC_ID, "2_ms", cb_rlc);
assert(rlc_hndl.success == true);

where cb_rlc is:

static void cb_rlc(sm_ag_if_rd_t const* rd, global_e2_node_id_t const* e2_node)
{
assert(rd != NULL);
assert(rd->type ==INDICATION_MSG_AGENT_IF_ANS_V0);

assert(rd->ind.type == RLC_STATS_V0);

rlc_ind_msg_t const* msg = &rd->ind.rlc.msg;

int64_t now = time_now_us_xapp_api();

printf("RLC ind_msg latency = %ld μs from E2-node type %d ID %d\n", now - msg->tstamp, e2_node->type, e2_node->nb_id.nb_id);
}

For PDCP:

  // PDCP subscribe
sm_ans_xapp_t pdcp_hndl = report_sm_xapp_api(e2_node, SM_PDCP_ID, "5_ms", cb_pdcp);
assert(pdcp_hndl.success == true);

where cb_pdcp is:

static void cb_pdcp(sm_ag_if_rd_t const* rd, global_e2_node_id_t const* e2_node)
{
assert(rd != NULL);
assert(rd->type == INDICATION_MSG_AGENT_IF_ANS_V0);

assert(rd->ind.type == PDCP_STATS_V0);

pdcp_ind_msg_t const* msg = &rd->ind.pdcp.msg;

int64_t now = time_now_us_xapp_api();

printf("PDCP ind_msg latency = %ld μs from E2-node type %d ID %d\n", now - msg->tstamp, e2_node->type, e2_node->nb_id.nb_id);
}

For GTP:

  // GTP subscribe
sm_ans_xapp_t gtp_hndl = report_sm_xapp_api(e2_node, SM_GTP_ID, "100_ms", cb_gtp);
assert(gtp_hndl.success == true);

where cb_gtp is:

static void cb_gtp(sm_ag_if_rd_t const* rd, global_e2_node_id_t const* e2_node)
{
assert(rd != NULL);
assert(rd->type ==INDICATION_MSG_AGENT_IF_ANS_V0);

assert(rd->ind.type == GTP_STATS_V0);

gtp_ind_msg_t const* msg = &rd->ind.gtp.msg;

int64_t now = time_now_us_xapp_api();

printf("GTP ind_msg latency = %ld μs from E2-node type %d ID %d\n", now - msg->tstamp, e2_node->type, e2_node->nb_id.nb_id);
}

For SLICE:

  // SLICE subscribe
sm_ans_xapp_t slice_hndl = report_sm_xapp_api(e2_node, SM_SLICE_ID, "1000_ms", cb_slice);
assert(slice_hndl.success == true);

where cb_slice is:

static void cb_slice(sm_ag_if_rd_t const* rd, global_e2_node_id_t const* e2_node)
{
assert(rd != NULL);
assert(rd->type ==INDICATION_MSG_AGENT_IF_ANS_V0);

assert(rd->ind.type == SLICE_STATS_V0);

slice_ind_msg_t const* msg = &rd->ind.slice.msg;

int64_t now = time_now_us_xapp_api();

printf("SLICE ind_msg latency = %ld μs from E2-node type %d ID %d\n", now - msg->tstamp, e2_node->type , e2_node->nb_id.nb_id);
}
warning

For each subscription there needs to be an unsubscription rm_report_sm_xapp_api() after the sleep time.

For each new service model subscription and callback added to the xApp code, the output of it should then change as follows (considering the case of having all the snippets showed above):

09:56:34.440086 [INFO]:  conf_file.c:443 Config -c file to ../conf_files/default.conf
09:56:34.440153 [INFO]: e42_xapp_api.c:81 Git SHA1 fea7dffd78de11f0d97ef16a0fda2937cbab544e
09:56:34.440156 [INFO]: e42_xapp.c:191 NearRT-RIC Server IP Address = 10.244.2.155, PORT = 36422
09:56:34.440199 [INFO]: emb_sm_ag.c:93 Loaded SM(s) 10, custom SMs true
09:56:34.440211 [INFO]: emb_sm_ric.c:93 Loaded SM(s) 10, custom SMs true
09:56:34.440824 [INFO]: msg_handler_xapp.c:533 E42 SETUP-REQUEST tx
09:56:34.441092 [INFO]: msg_handler_xapp.c:374 E42 SETUP-RESPONSE rx xApp ID 7
09:56:34.441098 [INFO]: msg_handler_xapp.c:390 Connected E2 Node(s) 1
Connected E2 nodes = 1
09:56:35.441302 [INFO]: msg_handler_xapp.c:568 RIC_SUBSCRIPTION_REQUEST tx RAN_FUNC_ID 142 RIC_REQ_ID 1
09:56:35.442617 [INFO]: msg_handler_xapp.c:155 RIC_SUBSCRIPTION_RESPONSE rx RAN_FUNC_ID 142 RIC_REQ_ID 1
09:56:35.442661 [INFO]: msg_handler_xapp.c:568 RIC_SUBSCRIPTION_REQUEST tx RAN_FUNC_ID 143 RIC_REQ_ID 2
MAC ind_msg latency = 1289 μs from E2-node type 2 ID 50
09:56:35.443975 [INFO]: msg_handler_xapp.c:155 RIC_SUBSCRIPTION_RESPONSE rx RAN_FUNC_ID 143 RIC_REQ_ID 2
09:56:35.444001 [INFO]: msg_handler_xapp.c:568 RIC_SUBSCRIPTION_REQUEST tx RAN_FUNC_ID 144 RIC_REQ_ID 3
09:56:35.444130 [INFO]: msg_handler_xapp.c:155 RIC_SUBSCRIPTION_RESPONSE rx RAN_FUNC_ID 144 RIC_REQ_ID 3
09:56:35.444145 [INFO]: msg_handler_xapp.c:568 RIC_SUBSCRIPTION_REQUEST tx RAN_FUNC_ID 148 RIC_REQ_ID 4
09:56:35.444262 [INFO]: msg_handler_xapp.c:155 RIC_SUBSCRIPTION_RESPONSE rx RAN_FUNC_ID 148 RIC_REQ_ID 4
09:56:35.444276 [INFO]: msg_handler_xapp.c:568 RIC_SUBSCRIPTION_REQUEST tx RAN_FUNC_ID 145 RIC_REQ_ID 5
MAC ind_msg latency = 1242 μs from E2-node type 2 ID 50
09:56:35.445371 [INFO]: msg_handler_xapp.c:155 RIC_SUBSCRIPTION_RESPONSE rx RAN_FUNC_ID 145 RIC_REQ_ID 5
MAC ind_msg latency = 1224 μs from E2-node type 2 ID 50
RLC ind_msg latency = 1229 μs from E2-node type 2 ID 50
MAC ind_msg latency = 1220 μs from E2-node type 2 ID 50
MAC ind_msg latency = 1223 μs from E2-node type 2 ID 50
RLC ind_msg latency = 1221 μs from E2-node type 2 ID 50
MAC ind_msg latency = 1223 μs from E2-node type 2 ID 50
PDCP ind_msg latency = 1217 μs from E2-node type 2 ID 50

Uninstall

To uninstall the network, use the following command:

brc remove network open-ran.yaml

Checking via the brc observe command, you should see that all the elements are removed.

💬 Lab Questions 💬

  1. What is the RAN_FUNC_ID for MAC, RLC, PDCP, GTP and SLICE service models.
  2. Subscribe to MAC, RLC, PDCP, GTP and SLICE service models with different periodicity.
  3. Find the UE RNTI (Radio Network Temporary Identifier). Hint: look at the file at path/to/your/xapp_sdk/include/src/sm/mac_sm/ie
  4. Find out the data model for RLC, PDCP, GTP and SLICE service models.

Advanced (optional)

This lab can also be performed leveraging an over-the-air deployment. In this section, we provide a sample deployment with a LITEON RU. Similarly to the previous deployment, this includes a 5G Standalone (SA) network with OpenAirInterface (OAI) gNB using a LITEON RU device, and Open5GS core network.

open-ran-liteon.yaml
apiVersion: athena.trirematics.io/v1
kind: Network
metadata:
name: bubbleran
namespace: trirematics
spec:
slices:
- plmn: "00101"
dnn: internet
network-mode: IPv4
service-type: eMBB
differentiator: 0x000000
ipv4-range: 12.1.1.0/24
ipv6-range: 2001:db8:1::/64
access:
- name: oai-gnb
stack: 5g-sa
model: oai-ran/monolithic-gnb-ru #cu-du-ru
identity:
an-id: 30
tracking-area: 1
radio:
device: oran-7.2
antenna:
formation: 4x4
scheduling:
nodeName: bubble2 # Change with any other CU-DU node
annotations:
# PCI IDs obtained by running `dpdk-devbind.py --status` in the CU-DU node
extras.t9s.io/pci-ids: '["0000:01:11.0", "0000:01:11.1"]'
# VF MACs obtained for dpdk0 interface by running `ip link show` in CU-DU node
extras.t9s.io/du-macs: '["02:11:22:33:44:66", "02:11:22:33:44:67"]'
# RU MAC (repeated) obtained by logging into RU and running `show eth-info`
extras.t9s.io/ru-macs: '["e8:c7:4f:25:80:f9", "e8:c7:4f:25:80:f9"]'
extras.t9s.io/mtu: '9000'
# RU IP used for logging into it
extras.t9s.io/o1-remote-ipv4: 192.168.1.40
# Flag for reboot, after first use, it can be set to `false` if values in 'cells' is not changed
extras.t9s.io/liteon-ru-reboot: 'true' #'false'
cells:
- band: n78
arfcn: 643296
bandwidth: 100MHz
subcarrier-spacing: 30kHz
tdd-config:
period: 2.5ms
dl-slots: 3
dl-symbols: 6
ul-slots: 1
ul-symbols: 4
controller: flexric.bubbleran
core-networks:
- ogscore.bubbleran
core:
- name: ogscore
stack: 5g-sa
model: open5gs/5gc
profiles:
- debug
identity:
region: 128
cn-group: 4
cn-id: 5
edge:
- name: flexric
stack: 5g-sa
model: mosaic5g/flexric
dns:
ipv4:
default: 8.8.8.8
secondary: 8.8.4.4

Deployment

Use the command brc install network open-ran-liteon.yaml to deploy the network. It should finish without errors and printout the three Kubernetes resource names that were created. Check for the status of the deployment using the command brc observe. Wait until all the Elements other than the UE are in the STATUS set to 1/1 Y state.

After the network has been successfully deployed, the monitoring xApp can be deployed following the exact same steps previously detailed.

warning

Before deploying this network first make sure:

  1. The CU-DU node name specified in the deployment file (in this case bubble2 on the section scheduling) is correct.
  2. The CU-DU node is able to reach the RU IP specified in the deployment file. If not sure about the IP, please check the Google Sheet (provided to all customers) with all IPs of your MX-PDK. To do so you may try to ssh into the RU from the node with the command ssh user@RU-IP replacing RU-IP with the actual IP.
  3. The CU-DU node is properly synchronized with the PTP grandmaster switch. To do so you may check the status with
sudo systemctl status ptp4l.service
sudo systemctl status phc2sys.service
  1. The CU-DU node's DPDK has been setup with
sudo dpdk-setup