O-RAN 5G Training: AuresTech and NextG Lab @ NC State


This site provides information on the 5G and O-RAN course offered by AuresTech in conjunction with NextG Wireless Lab at NC State. This course offers a comprehensive experience that blends in-depth theoretical instruction with practical, hands-on labs to foster innovation in 5G and Open RAN (O-RAN) technologies. It also covers AuresTech tactical 5G concept.
Designed for engineers, researchers, and technology leaders, the course covers core concepts of 5G architecture, the modular design of O-RAN systems, xApps and rApps. Participants will gain practical experience through guided labs where they configure, deploy, and test real-world 5G components and O-RAN interfaces.
By bridging academic knowledge with real-world application, this program equips participants with the skills and insights needed to drive innovation in secure, open, and interoperable 5G networks.
Session 1 - Setup Requirements
System Requirements
Operating System: Ubuntu 24.04 LTS (x86, 64-bit)
ARM devices such as MacBook M1 (Apple silicon) are unsupported!
CPU: Ideally 12+ cores
We will be running the core network, base station and the user on one system, so more cores is better.
The system can technically operate on as low as 4 cores, but network throughput and reliability will be low.
RAM: 8GB
Running everything at once takes about 4GB of RAM.
Install Dependencies
sudo apt install -y git vim tree net-tools libsctp-dev python3 cmake-curses-gui libpcre2-dev build-essential cmake libfftw3-dev libmbedtls-dev libboost-program-options-dev libconfig++-dev libtool autoconf python3-pip curl bison flex iperf unzip
Install Swig 4.1
cd ~
git clone https://github.com/swig/swig.git
cd swig
git checkout release-4.1
./autogen.sh
./configure --prefix=/usr/
make -j`nproc`
sudo make install
Check GCC Version (gcc-10, gcc-12, or gcc-13)
gcc --version
Warning
If you see that you have gcc 11, follow the steps given here to switch to a different version
Install Docker Compose
sudo apt install -y putty ca-certificates gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker.io docker-buildx-plugin docker-compose-plugin
Check docker compose version. The installed version should be v2.38.2
, as of the release of this guide.
sudo docker compose version
Note
Optional Step: If you do not want to use sudo while executing docker compose commands, run sudo usermod -a -G docker $(whoami)
and reboot
the machine.
Session 2 - OAI Installation
Before going into the background, we will begin downloading the core network components, which may take some time. Run the following commands:
cd ~
wget -O ~/oai-cn5g.zip https://gitlab.eurecom.fr/oai/openairinterface5g/-/archive/develop/openairinterface5g-develop.zip?path=doc/tutorial_resources/oai-cn5g
unzip ~/oai-cn5g.zip
mv ~/openairinterface5g-develop-doc-tutorial_resources-oai-cn5g/doc/tutorial_resources/oai-cn5g ~/oai-cn5g
rm -r ~/openairinterface5g-develop-doc-tutorial_resources-oai-cn5g ~/oai-cn5g.zip
cd ~/oai-cn5g
sudo docker compose pull
Refer to the presentation (O-RAN background slides 4-10)
1G-5G timeline
Reference Cellular Architecture (RAN)
Evolution of RAN (vRAN and O-RAN)
Open RAN (Radio Access Network) is a virtualized, disaggregated design paradigm for 5G/6G cellular networks. It refers to the concept of a cellular network that is built on open interfaces and modular components.
Open RAN is important because it…
enables networks built from components by multiple vendors, replacing the current proprietary systems belonging to each vendor and mobile carrier
allows vendors to share physical and cloud resources in the same cell tower
is desirable as an industry standard; for example, the U.S. has awarded over $100M to Open RAN projects this year.
O-RAN is Open RAN as defined by O-RAN ALLIANCE, which is a worldwide community of mobile network operators, vendors, and research & academic institutions. “O-RAN ALLIANCE’s mission is to re-shape the RAN industry towards more intelligent, open, virtualized and fully interoperable mobile networks.”
Thare are various open-source software solutions built upon the 5G and O-RAN standards. The one we will use in this training is OpenAirInterface or OAI, which is a software stack providing the components needed for an operational radio access network purely implemented in software.
The major components are:
The 5G core network
The gNB / gNodeB / base station (BS)
In O-RAN, the gNB can be split between the Centralized Unit (CU) and the Distributed Unit (DU), where the CU contains the higher-level layers of the network protocol stack and the DU contains the lower layers.
The user equipment (UE)
Setup OAI 5G Core Network
In this demo, we will employ the Core Network solution provided by OpenAirInterface. This solution deploys network functions as Docker containers. The CN components can be customized according to experimental requirements by modifying the configuration files. However, for the purposes of this tutorial we retain the default functionality.
Before the background section we began downloading the Docker images for the core network services.
Test the deployment of Core Network
sudo docker compose up -d

Verify that all the 10 containers are deployed and healthy
.
sudo docker ps -a

Turn the core network off. We will start it again later when the rest of the network is ready.
sudo docker compose down
If you prefer seeing real-time container logs streaming to your terminal, run the docker compose command without the -d flag. In that mode, pressing Ctrl+C will stop all containers!
Setup OAI Radio Access Network and UE
Clone the OAI 5G RAN repository from GitLab, and check out the develop branch.
git clone https://gitlab.eurecom.fr/oai/openairinterface5g.git ~/oai
cd ~/oai
git checkout develop
Install OAI dependencies and build the binaries for the OAI base station (gNB) and the OAI user (UE).
cd ~/oai/cmake_targets/
sudo ./build_oai -I -w SIMU --gNB --nrUE --ninja
This installs a couple important packages required by OAI gNB and UE, including libsctp-dev, libtool, SIMDE and most importantly, ASN.1. The installation logs are not displayed on your terminal, it will be written to file in your log directory, double check to ensure ASN.1 was installed properly.
In a real world system, the core, gNB, and UE would be located on separate systems, but for simplicity we will run everything together on one system.
Note that we compile with the -w SIMU option which means that we want to use a simulated radio instead of a physical radio such as a USRP software-defined radio. For a USRP we would change this to -w USRP.
Setup FlexRIC
We also need to install the component FlexRIC, which we will use in a later session.
Clone the OAI 5G RAN repository and checkout the beabdd07
commit.
git clone https://github.com/openaicellular/flexric.git ~/flexric
cd ~/flexric
git checkout beabdd07
Build the flexRIC module.
mkdir build
cd build
cmake ../

make -j`nproc`
sudo make install
Session 3 - Using RFSimulator with OAI
Deploy 5G Network
Start the Core Network
In terminal 1
,
cd ~/oai-cn5g
sudo docker compose up
Start the gNB
In terminal 2
,
cd ~/oai/cmake_targets/ran_build/build
sudo ./nr-softmodem -O ../../../targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.usrpb210.conf --gNBs.[0].min_rxtxtime 6 --rfsim --sa

Note
Although the configuration is written for the USRP B210, we have added the --rfsim
argument which enables a simulated RF frontend which streams analog I/Q data over a TCP connection.
Start the UE
In terminal 3
,
cd ~/oai/cmake_targets/ran_build/build
sudo ./nr-uesoftmodem -r 106 --numerology 1 --band 78 -C 3619200000 --rfsim --uicc0.imsi 001010000000001 --rfsimulator.serveraddr 127.0.0.1

Note
Observe that when the user connects, it reports information such as the IP address (10.0.0.2) and the RSRP (Reference Signal Received Power).
A base station can have multiple cells which are tied to antennas pointing in different directions. These cells can operate in different frequencies and traffic can be scheduled differently between them. When the user starts, it scans the spectrum for cells that it can connect to. A cell can host multiple users, and as such, we prefer to start the base station first and then any users afterwards.
Exchange traffic between Network and UE
We can exchange both downlink and uplink traffic in our network. Downlink refers to communications from the nodeB to the UE, while uplink is communications from UE to nodeB.
Streaming Traffic using Ping
For uplink ping - UE to network
In terminal 4
,
ping 192.168.70.135 -I oaitun_ue1
For Downlink ping - Network to UE
sudo docker exec -it oai-ext-dn ping <ue_ip>
Note
Replace <ue_ip>
with the IP address of the UE which is output in the terminal window. It should be an IP address similar to 10.0.0.2
Use ctrl+c
or ctrl+d
to stop/exit the ping processes.
Streaming Traffic with iPerf
Downlink iPerf
Find out the IP address of the UE by running ifconfig
on the UE machine and check the IP address field of oaitun_ue1
network Interface. Here we initialize an iperf server for UDP traffic.
In terminal 4
,
iperf -s -u -i 1 -B <ue_ip>
The below command generates UDP traffic continuously at the rate of 10Mbps from the Core network. In terminal 5
,
sudo docker exec -it oai-ext-dn iperf -u -t 0 -i 1 -fk -B 192.168.70.135 -b 10M -c <ue_ip>
Uplink iperf
On terminal 4
, initialize the iperf server (metrics are printed every second) for TCP traffic run,
sudo docker exec -it oai-ext-dn iperf -s -i 1 -fk -B 192.168.70.135
In terminal 5
, run
iperf -c 192.168.70.135 -i 1 -b 10M -t 0 -B <ue_ip>
Note
Replace <ue_ip>
with the IP address of the UE which is output in the terminal window. It should be an IP address similar to 10.0.0.2
OAI Configuration File
When starting the base station, we pointed it to the gnb.sa.band78.fr1.106PRB.usrpb210.conf
file. Let us look at some of configurable
parameters in OAI:
plmn_list = ({ mcc = 001; mnc = 01; mnc_length = 2; snssaiList = ({ sst = 1; }) });
A public land mobile network (PLMN) refers to a specific carrier’s cellular network in a given country. A PLMN ID consists of the mobile country code (MCC) and mobile network code (MNC), and the combination of 001 and 01 denotes a test network.
gNB_ID = 0xe00;
gNB_name = "gNB-OAI";
The 22-bit gNB ID should be unique to each base station in a given PLMN.
For each cell we can configure the channels for downlink and uplink communications.
# downlink
dl_frequencyBand = 78; # NR band 78 corresponds to 3300MHz-3800MHz, and is TDD (time division duplex)
dl_subcarrierSpacing = 1; # index 0-6, index 1 corresponds to 30kHz
# subcarrier spacing determines how long an OFDM symbol lasts, higher frequency means shorter symbol -> lower latency
dl_carrierBandwidth = 106; # number of physical resource blocks (transmitted at once across the frequency domain)
# uplink
ul_frequencyBand = 78;
ul_subcarrierSpacing = 1;
ul_carrierBandwidth = 106;
# TDD parameters (for time division duplex bidirectional data is transmitted on the same frequency, alternating over time between downlink and uplink)
nrofDownlinkSlots = 7;
nrofDownlinkSymbols = 6;
nrofUplinkSlots = 2;
nrofUplinkSymbols = 4;
Session 4+5 - O-RAN FlexRIC Demonstration
O-RAN is the Open RAN as defined by O-RAN ALLIANCE, which is a worldwide community of mobile network operators, vendors, and research & academic institutions. “O-RAN ALLIANCE’s mission is to re-shape the RAN industry towards more intelligent, open, virtualized and fully interoperable mobile networks.”
Founded by AT&T, China Mobile, Deutsche Telekom, NTT DOCOMO and Orange in Feb 2018
Based on two core principles: Openness, Intelligence
Flexibility by design
Open interfaces and APIs
RAN Intelligent Controllers (RICs)
Abstract the networks
Allow telecom operators to implement custom control logic
For more information, refer to the presentation (O-RAN background slides 11-37)
Previously, we installed FlexRIC in the process of building and running OAI. However, we disabled E2 support in OAI, so we need to recompile the base station and user with E2 enabled.
cd ~/oai/cmake_targets/
sudo ./build_oai -I -w SIMU --gNB --nrUE --build-e2 --ninja
We will follow the same steps as in Session 3, except this time we will also run the near-RT RIC and some xApps.
Deploy 5G Network
Start the Core Network
In terminal 1
,
cd ~/oai-cn5g
sudo docker compose up
Start the gNB
In terminal 2
,
cd ~/oai/cmake_targets/ran_build/build
sudo ./nr-softmodem -O ../../../targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.usrpb210.conf --gNBs.[0].min_rxtxtime 6 --rfsim --sa

Note
Although the configuration is written for the USRP B210, we have added the --rfsim
argument which enables a simulated RF frontend which streams analog I/Q data over a TCP connection.
Start the UE
In terminal 3
,
cd ~/oai/cmake_targets/ran_build/build
sudo ./nr-uesoftmodem -r 106 --numerology 1 --band 78 -C 3619200000 --rfsim --uicc0.imsi 001010000000001 --rfsimulator.serveraddr 127.0.0.1

Note
Observe that when the user connects, it reports information such as the IP address (10.0.0.2) and the RSRP (Reference Signal Received Power).
Start the near-RT RIC
In terminal 3
,
cd ~/
./flexric/build/examples/ric/nearRT-RIC


Streaming Traffic with iPerf
Uplink iperf
On terminal 4
, initialize the iperf server (metrics are printed every second) for TCP traffic run,
sudo docker exec -it oai-ext-dn iperf -s -i 1 -fk -B 192.168.70.135
In terminal 5
, run
iperf -c 192.168.70.135 -i 1 -b 10M -t 0 -B <ue_ip>
Note
Replace <ue_ip>
with the IP address of the UE which is output in the terminal window. It should be an IP address similar to 10.0.0.2
Session 6 - xApp onboarding, deployment
Background
An xApp is simply an application that is deployed to the near-real-time RAN Intelligent Controller (RIC) and is capable of communicating to the RAN. An xApp can be developed in any programming language, but to be O-RAN compliant, it needs to be able to communicate over the E2 interface to E2 nodes. An E2 Node refers to a component of the RAN that can interface with the RIC via E2, usually referring to the base station (DU/CU). Note that the user has no direct connection to the E2 interface; when we refer to the RAN in this context, we generally mean the base station.

There is also a non-real-time RIC; applications stored in the non-real-time RIC are called rApps instead. Near-real-time applications are defined as running within 10ms to 1 second, while a non-real-time application takes longer than 1 second.
Below is a comparison between xApps and rApps:

What problems in 5G networks can xApps can solve?
Spectrum sharing - Ensuring multiple networks on the same frequency do not interfere with each other
Interference detection and mitigation - detecting interference through performance metrics or spectrograms and moving communications away from interfered frequencies
Radar detection - in order to avoid interference between radar and 5G networks, see SenseORAN
Anomaly detection - identifying users in the network which maliciously disrupt the RAN or other xApps from working properly
Traffic steering - looking at user behavior and connecting them to different cells in the RAN for load balancing or energy saving
How do xApps work in the RIC?
To understand how an xApp works, first we must look at how an O-RAN network is implemented. The RAN Intelligent Controller (RIC) is capable of dynamically controlling the RAN. The near-RT RIC that we will use with OAI is called FlexRIC.
FlexRIC consists of a collection of microservices, which are small containerized programs. These containers are created and deployed using Docker, using an image that we prepare beforehand that consists of everything needed for the application to start instantly. FlexRIC acts as a message broker between xApps and E2 nodes by providing the E2 termination, the point at which all the xApps and E2 nodes connect to communicate with each other. The xApps we will use are hosted on a single Docker container, but several microservices could make up a single xApp.
The E2 interface is based on the O-RAN ALLIANCE’s specifications. Messages are encoded through the ASN.1 standard, which allows messages to be encoded in binary according to a specification file. ASN.1 encoding and decoding is implemented for many programming languages, allowing E2 and xApp specifications to be theoretically language-agnostic.
xApps can define the contents of the data that they send and receive by writing an ASN.1 specification. We refer to an xApp’s message specification as a service model.
Below is an excerpt of example ASN.1 syntax:
Metrics ::= SEQUENCE {
MCS INTEGER (0.. 28, ...) OPTIONAL,
bitrateMbps INTEGER (0.. 65535, ...) OPTIONAL,
...
}
DownlinkMetrics ::= SEQUENCE {
CQI INTEGER (0.. 15, ...) OPTIONAL,
metrics Metrics,
...
}
IndicationMessage ::= SEQUENCE {
Payload DownlinkMetrics
}
The O-RAN specifications are available on the O-RAN ALLIANCE’s website: https://specifications.o-ran.org/specifications Some examples of E2 service models which are standardized by the O-RAN ALLIANCE include:
E2SM-KPM, which allows the RAN to send key performance metrics to an xApp,
such as the bitrate and error rate for each user
E2SM Cell Configuration and Control, which allows an xApp to control the RAN at the node and cell levels
E2SM RAN control, which allows an xApp to control the RAN at the UE and cell levels
xApps are persistent in the RIC and run continuously. Since the RIC can be connected to multiple RANs, xApps wait for base stations to connect. When connecting to the RIC, the RAN must subscribe to any xApps that it wants to communicate with.


Run xApps
KPIMON xApp
First we will run the KPIMON xApp and observe some metrics. This xApp is based on the E2SM-KPM (Key Performance Metrics)
service model. It is responsible for collecting metrics collected by the RAN and forwarding it to relevant xApps to help in RAN control.
The KPM service model sends data from the RAN to the RIC, which we call an indication message.
When we want the RIC to control the RAN, we must send a control message from the xApp, but in this case, we only receive indication messages.
Per O-RAN specifications, 5G measurements supported by KPM are specified in 3GPP TS 28.552. Some of the metrics supported are DRB.PdcpSduVolumeDL
, DRB.PdcpSduVolumeUL
, DRB.RlcSduDelayDl
, DRB.UEThpDl
, DRB.UEThpUl
, RRU.PrbTotDl
, RRU.PrbTotUl
.
In this implementation Report Style 4 (Section 7.4.5) has been considered.
In a new Terminal, run
cd ~/flexric
./build/examples/xApp/c/monitor/xapp_kpm_moni
The output should look like this:

On the gNB side:

Behind the scenes
FlexRIC supports writing xApps in C or Python.
Writing an xApp in C is fairly simple. Looking at the KPIMON xApp:
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_t nodes = e2_nodes_xapp_api();
defer({ free_e2_node_arr(&nodes); });
assert(nodes.len > 0);
printf("Connected E2 nodes = %d\n", nodes.len);
// KPM indication
sm_ans_xapp_t* kpm_handle = NULL;
if(nodes.len > 0){
kpm_handle = calloc( nodes.len, sizeof(sm_ans_xapp_t) );
assert(kpm_handle != NULL);
}
for (int i = 0; i < nodes.len; i++) {
e2_node_connected_t* n = &nodes.n[i];
for (size_t j = 0; j < n->len_rf; j++)
printf("Registered node %d ran func id = %d \n ", i, n->ack_rf[j].id);
////////////
// START KPM
////////////
kpm_sub_data_t kpm_sub = {0};
defer({ free_kpm_sub_data(&kpm_sub); });
// KPM Event Trigger
uint64_t period_ms = 1000;
kpm_sub.ev_trg_def = gen_ev_trig(period_ms);
printf("[xApp]: reporting period = %lu [ms]\n", period_ms);
// KPM Action Definition
kpm_sub.sz_ad = 1;
kpm_sub.ad = calloc(1, sizeof(kpm_act_def_t));
assert(kpm_sub.ad != NULL && "Memory exhausted");
// ...
const int KPM_ran_function = 2;
// sm_cb_kpm is our callback function when a KPM indication message is received
kpm_handle[i] = report_sm_xapp_api(&nodes.n[i].id, KPM_ran_function, &kpm_sub, sm_cb_kpm);
assert(kpm_handle[i].success == true);
}
static
void sm_cb_kpm(sm_ag_if_rd_t const* rd)
{
assert(rd != NULL);
assert(rd->type == INDICATION_MSG_AGENT_IF_ANS_V0);
assert(rd->ind.type == KPM_STATS_V3_0);
// Reading Indication Message Format 3
kpm_ind_data_t const* ind = &rd->ind.kpm.ind;
kpm_ric_ind_hdr_format_1_t const* hdr_frm_1 = &ind->hdr.kpm_ric_ind_hdr_format_1;
kpm_ind_msg_format_3_t const* msg_frm_3 = &ind->msg.frm_3;
int64_t const now = time_now_us();
static int counter = 1;
{
lock_guard(&mtx);
printf("\n%7d KPM ind_msg latency = %ld [μs]\n", counter, now - hdr_frm_1->collectStartTime); // xApp <-> E2 Node
// Reported list of measurements per UE
for (size_t i = 0; i < msg_frm_3->ue_meas_report_lst_len; i++)
{
switch (msg_frm_3->meas_report_per_ue[i].ue_meas_report_lst.type)
{
case GNB_UE_ID_E2SM:
if (msg_frm_3->meas_report_per_ue[i].ue_meas_report_lst.gnb.gnb_cu_ue_f1ap_lst != NULL)
{
for (size_t j = 0; j < msg_frm_3->meas_report_per_ue[i].ue_meas_report_lst.gnb.gnb_cu_ue_f1ap_lst_len; j++)
{
printf("UE ID type = gNB-CU, gnb_cu_ue_f1ap = %u\n", msg_frm_3->meas_report_per_ue[i].ue_meas_report_lst.gnb.gnb_cu_ue_f1ap_lst[j]);
}
}
else
{
printf("UE ID type = gNB, amf_ue_ngap_id = %lu\n", msg_frm_3->meas_report_per_ue[i].ue_meas_report_lst.gnb.amf_ue_ngap_id);
}
break;
case GNB_DU_UE_ID_E2SM:
printf("UE ID type = gNB-DU, gnb_cu_ue_f1ap = %u\n", msg_frm_3->meas_report_per_ue[i].ue_meas_report_lst.gnb_du.gnb_cu_ue_f1ap);
break;
default:
assert(false && "UE ID type not yet implemented");
}
kpm_ind_msg_format_1_t const* msg_frm_1 = &msg_frm_3->meas_report_per_ue[i].ind_msg_format_1;
// UE Measurements per granularity period
for (size_t j = 0; j<msg_frm_1->meas_data_lst_len; j++)
{
for (size_t z = 0; z<msg_frm_1->meas_data_lst[j].meas_record_len; z++)
{
if (msg_frm_1->meas_info_lst_len > 0)
{
switch (msg_frm_1->meas_info_lst[z].meas_type.type)
{
case NAME_MEAS_TYPE:
{
// Get the Measurement Name
char meas_info_name_str[msg_frm_1->meas_info_lst[z].meas_type.name.len + 1];
memcpy(meas_info_name_str, msg_frm_1->meas_info_lst[z].meas_type.name.buf, msg_frm_1->meas_info_lst[z].meas_type.name.len);
meas_info_name_str[msg_frm_1->meas_info_lst[z].meas_type.name.len] = '\0';
// Get the value of the Measurement
switch (msg_frm_1->meas_data_lst[j].meas_record_lst[z].value)
{
case REAL_MEAS_VALUE:
{
if (strcmp(meas_info_name_str, "DRB.RlcSduDelayDl") == 0)
{
printf("DRB.RlcSduDelayDl = %.2f [μs]\n", msg_frm_1->meas_data_lst[j].meas_record_lst[z].real_val);
}
else if (strcmp(meas_info_name_str, "DRB.UEThpDl") == 0)
{
printf("DRB.UEThpDl = %.2f [kbps]\n", msg_frm_1->meas_data_lst[j].meas_record_lst[z].real_val);
}
else if (strcmp(meas_info_name_str, "DRB.UEThpUl") == 0)
{
printf("DRB.UEThpUl = %.2f [kbps]\n", msg_frm_1->meas_data_lst[j].meas_record_lst[z].real_val);
}
else
{
printf("Measurement Name not yet implemented %s\n", meas_info_name_str);
//assert(false && "Measurement Name not yet implemented");
}
break;
}
case INTEGER_MEAS_VALUE:
{
if (strcmp(meas_info_name_str, "RRU.PrbTotDl") == 0)
{
printf("RRU.PrbTotDl = %d [PRBs]\n", msg_frm_1->meas_data_lst[j].meas_record_lst[z].int_val);
}
else if (strcmp(meas_info_name_str, "RRU.PrbTotUl") == 0)
{
printf("RRU.PrbTotUl = %d [PRBs]\n", msg_frm_1->meas_data_lst[j].meas_record_lst[z].int_val);
}
else if (strcmp(meas_info_name_str, "DRB.PdcpSduVolumeDL") == 0)
{
printf("DRB.PdcpSduVolumeDL = %d [kb]\n", msg_frm_1->meas_data_lst[j].meas_record_lst[z].int_val);
}
else if (strcmp(meas_info_name_str, "DRB.PdcpSduVolumeUL") == 0)
{
printf("DRB.PdcpSduVolumeUL = %d [kb]\n", msg_frm_1->meas_data_lst[j].meas_record_lst[z].int_val);
}
else
{
printf("Measurement Name not yet implemented %s\n", meas_info_name_str);
//assert(false && "Measurement Name not yet implemented");
}
break;
}
default:
assert(0 != 0 && "Value not recognized");
}
break;
}
case ID_MEAS_TYPE:
printf(" ID_MEAS_TYPE \n");
break;
default:
assert(false && "Measurement Type not yet implemented");
}
}
if (msg_frm_1->meas_data_lst[j].incomplete_flag && *msg_frm_1->meas_data_lst[j].incomplete_flag == TRUE_ENUM_VALUE)
printf("Measurement Record not reliable");
}
}
}
counter++;
}
}
RAN Control (RC) xApp
This xApp enables control of RAN services exposed by the RAN. The current implementation exposes RAN control function QoS flow mapping configuration
. This version of the xApp supports REPORT Service Style 4
(UE Information - section 7.4.5) - aperiodic subscription for UE RRC State Change
and CONTROL Service Style 1
(“Radio Bearer Control” - section 7.6.2) - “QoS flow mapping configuration” (e.g creating a new DRB).
cd ~/flexric
./build/examples/xApp/c/kpm_rc/xapp_kpm_rc
If we observe the C code:
// START RC
////////////
// RC On Demand report
// rc_sub_data_t rc_sub = {0};
// defer({ free_rc_sub_data(&rc_sub); });
// sm_ans_xapp_t h_2 = report_sm_xapp_api(&nodes.n[0].id, RC_ran_function, &rc_sub, sm_cb_rc);
// assert(h_2.success == true);
// RC Control
rc_ctrl_req_data_t rc_ctrl = {0};
rc_ctrl.hdr = gen_rc_ctrl_hdr();
rc_ctrl.msg = gen_rc_ctrl_msg();
const int RC_ran_function = 3;
for(size_t i =0; i < nodes.len; ++i){
ngran_node_t const t = nodes.n[i].id.type;
if(t == ngran_gNB || t == ngran_gNB_CU)
control_sm_xapp_api(&nodes.n[i].id, RC_ran_function, &rc_ctrl);
}
free_rc_ctrl_req_data(&rc_ctrl);
Investigating gen_rc_ctrl_hdr and gen_rc_ctrl_msg allows us to see what messages we are sending:
// this enum provides all the potential types of RAN control that this xApp can do.
typedef enum {
DRB_QoS_Configuration_7_6_2_1 = 1,
QoS_flow_mapping_configuration_7_6_2_1 = 2,
Logical_channel_configuration_7_6_2_1 = 3,
Radio_admission_control_7_6_2_1 = 4,
DRB_termination_control_7_6_2_1 = 5,
DRB_split_ratio_control_7_6_2_1 = 6,
PDCP_Duplication_control_7_6_2_1 = 7,
} rc_ctrl_service_style_1_e;
static
e2sm_rc_ctrl_hdr_frmt_1_t gen_rc_ctrl_hdr_frmt_1(void)
{
e2sm_rc_ctrl_hdr_frmt_1_t dst = {0};
// 6.2.2.6
{
lock_guard(&mtx);
dst.ue_id = cp_ue_id_e2sm(&ue_id);
}
// CONTROL Service Style 1: Radio Bearer Control
dst.ric_style_type = 1;
// QoS flow mapping conf
dst.ctrl_act_id = QoS_flow_mapping_configuration_7_6_2_1 ;
return dst;
}
static
e2sm_rc_ctrl_hdr_t gen_rc_ctrl_hdr(void)
{
e2sm_rc_ctrl_hdr_t dst = {0};
// Radio Bearer Control
dst.format = FORMAT_1_E2SM_RC_CTRL_HDR;
dst.frmt_1 = gen_rc_ctrl_hdr_frmt_1();
return dst;
}
typedef enum {
DRB_ID_8_4_2_2 = 1,
LIST_OF_QOS_FLOWS_MOD_IN_DRB_8_4_2_2 = 2,
QOS_FLOW_ITEM_8_4_2_2 = 3,
QOS_FLOW_ID_8_4_2_2 = 4,
QOS_FLOW_MAPPING_IND_8_4_2_2 = 5,
} qos_flow_mapping_conf_e;
static
e2sm_rc_ctrl_msg_frmt_1_t gen_rc_ctrl_msg_frmt_1_qos_flow_map()
{
e2sm_rc_ctrl_msg_frmt_1_t dst = {0};
// 8.4.2.2 QoS flow mapping configuration
dst.sz_ran_param = 2;
dst.ran_param = calloc(2, sizeof(seq_ran_param_t));
assert(dst.ran_param != NULL && "Memory exhausted");
dst.ran_param[0].ran_param_id = DRB_ID_8_4_2_2;
dst.ran_param[0].ran_param_val.type = ELEMENT_KEY_FLAG_TRUE_RAN_PARAMETER_VAL_TYPE;
dst.ran_param[0].ran_param_val.flag_true = calloc(1, sizeof(ran_parameter_value_t)) ;
assert(dst.ran_param[0].ran_param_val.flag_true != NULL && "Memory exhausted");
// Let's suppose that it is the DRB 5
dst.ran_param[0].ran_param_val.flag_true->type = INTEGER_RAN_PARAMETER_VALUE;
dst.ran_param[0].ran_param_val.flag_true->int_ran = 5;
// List of QoS Flows to be modified in DRB
dst.ran_param[1].ran_param_id = LIST_OF_QOS_FLOWS_MOD_IN_DRB_8_4_2_2;
dst.ran_param[1].ran_param_val.type = LIST_RAN_PARAMETER_VAL_TYPE;
dst.ran_param[1].ran_param_val.lst = calloc(1, sizeof(ran_param_list_t));
assert(dst.ran_param[1].ran_param_val.lst != NULL && "Memory exhausted");
ran_param_list_t* rpl = dst.ran_param[1].ran_param_val.lst;
rpl->sz_lst_ran_param = 1;
rpl->lst_ran_param = calloc(1, sizeof(lst_ran_param_t));
assert(rpl->lst_ran_param != NULL && "Memory exhausted");
// QoS Flow Item
// Bug in the standard. RAN Parameter List 9.3.13
// has a mandatory ie RAN Parameter ID 9.3.8
// and a mandatory ie RAN Parameter Structure 9.3.12
// However, the ASN
// RANParameter-LIST ::= SEQUENCE {
// list-of-ranParameter SEQUENCE (SIZE(1..maxnoofItemsinList)) OF RANParameter-STRUCTURE,
// ..
// }
//
// Misses RAN Parameter ID and only has RAN Parameter Structure
// rpl->lst_ran_param[0].ran_param_id = QOS_FLOW_ITEM_8_4_2_2;
rpl->lst_ran_param[0].ran_param_struct.sz_ran_param_struct = 2;
rpl->lst_ran_param[0].ran_param_struct.ran_param_struct = calloc(2, sizeof(seq_ran_param_t));
assert(rpl->lst_ran_param[0].ran_param_struct.ran_param_struct != NULL && "Memory exhausted");
seq_ran_param_t* rps = rpl->lst_ran_param[0].ran_param_struct.ran_param_struct ;
// QoS Flow Identifier
rps[0].ran_param_id = QOS_FLOW_ID_8_4_2_2;
rps[0].ran_param_val.type = ELEMENT_KEY_FLAG_TRUE_RAN_PARAMETER_VAL_TYPE;
rps[0].ran_param_val.flag_true = calloc(1, sizeof(ran_parameter_value_t));
assert(rps[0].ran_param_val.flag_true != NULL && "Memory exhausted");
rps[0].ran_param_val.flag_true->type = INTEGER_RAN_PARAMETER_VALUE;
// Let's suppose that we have QFI 10
rps[0].ran_param_val.flag_true->int_ran = 10;
// QoS Flow Mapping Indication
rps[1].ran_param_id = QOS_FLOW_MAPPING_IND_8_4_2_2;
rps[1].ran_param_val.type = ELEMENT_KEY_FLAG_FALSE_RAN_PARAMETER_VAL_TYPE;
rps[1].ran_param_val.flag_false = calloc(1, sizeof(ran_parameter_value_t));
assert(rps[1].ran_param_val.flag_false != NULL && "Memory exhausted");
// ENUMERATED (ul, dl, ...)
rps[1].ran_param_val.flag_false->type = INTEGER_RAN_PARAMETER_VALUE;
rps[1].ran_param_val.flag_false->int_ran = 1;
return dst;
}
static
e2sm_rc_ctrl_msg_t gen_rc_ctrl_msg(void)
{
e2sm_rc_ctrl_msg_t dst = {0};
// Radio Bearer Control
dst.format = FORMAT_1_E2SM_RC_CTRL_MSG;
//dst.frmt_1 = gen_rc_ctrl_msg_frmt_1();
dst.frmt_1 = gen_rc_ctrl_msg_frmt_1_qos_flow_map();
return dst;
}
Session 7 - Additional Experiments
How can we use SDRs and over-the-air communiation with OAI?
The USRP B210 software-defined radio is capable of acting as the RF frontend for both the base station and user. It connects to a computer over USB 3.
In order to use a USRP SDR with OAI we need to build and install the UHD drivers. First we install any required packages:
sudo apt install -y autoconf automake build-essential ccache cmake cpufrequtils doxygen ethtool g++ git inetutils-tools libboost-all-dev libncurses-dev libusb-1.0-0 libusb-1.0-0-dev libusb-dev python3-dev python3-mako python3-numpy python3-requests python3-scipy python3-setuptools python3-ruamel.yaml
Then we can download the USRP driver source code and build it on our system:
git clone https://github.com/EttusResearch/uhd.git ~/uhd
cd ~/uhd
git checkout v4.8.0.0
cd host
mkdir build
cd build
cmake ../
make -j $(nproc)
sudo make install
sudo ldconfig
The USRP B210 firmware is not stored on the device itself and is loaded over USB each time the device is plugged in and a program connects to the USRP. As such, we need to download the compatible USRP firmware images to our system.
sudo uhd_images_downloader
Note that for a USRP X310 (which connects through Ethernet instead), as long as the device is not corrupted or disconnected during an update, the firmware should persist even when power is off.
When running the base station and UE, we need to make some slight changes to the commands:
sudo ./nr-softmodem -O ../../../targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.usrpb210.conf --gNBs.[0].min_rxtxtime 6 --sa -E -continuous-tx
sudo ./nr-uesoftmodem -r 106 --numerology 1 --band 78 -C 3619200000 --uicc0.imsi 001010000000001 --ue-fo-compensation -E
How can we connect multiple users at once in OAI?
Over the air, multiple users should be able to connect to the base station by default.
However, for RFsimulator, we need to also isolate the users from each other in the system such that their network access isn’t shared.
OAI provides a script multi_ue.sh
which handles creating the environments for the users.
The details can be found in OAI’s documentation.
Documentation written by Nathan Stephenson and supported by NextG Wireless Lab @ NC State and AuresTech. Some resources for this website are based off of or taken from the Open AI Cellular repository