Merge remote-tracking branch 'origin/gtp-e1-fix-errors-test' into integration_2025_w44 (!3725)

Handle GTP receiver errors, fix memory leaks, add CU-UP test

- Fix use-after-free bugs in GTP by properly stopping the receive thread(s)
- Fix a bug from !3519 (merged): QFI 0 is a valid QFI
- Fix multiple memory leaks in CU-UP, E1, CU-UP load tester, SCTP
- Add a (conservative) functional CU-UP test that verifies that the load tester
  and CU-UP work. Test only a low throughput, as it will run in parallel with
  other tests.
This commit is contained in:
Jaroslava Fiedlerova
2025-10-30 10:41:47 +01:00
13 changed files with 84 additions and 30 deletions
+4 -1
View File
@@ -150,7 +150,8 @@ int main(int argc, char **argv)
time_manager_start(tick_functions, tick_functions_count, TIME_SOURCE_REALTIME);
// strdup to put the sring in the core file for post mortem identification
LOG_I(HW, "Version: %s\n", strdup(OAI_PACKAGE_VERSION));
char *v = strdup(OAI_PACKAGE_VERSION);
LOG_I(HW, "Version: %s\n", v);
set_softmodem_sighandler();
itti_init(TASK_MAX, tasks_info);
int rc;
@@ -189,6 +190,8 @@ int main(int argc, char **argv)
time_manager_finish();
free(v);
logClean();
printf("Bye.\n");
return 0;
+5 -5
View File
@@ -125,8 +125,8 @@ static void e1_task_handle_sctp_data_ind(instance_t instance, sctp_data_ind_t *s
int e1ap_send_RESET(bool isCu, sctp_assoc_t assoc_id, E1AP_Reset_t *Reset)
{
AssertFatal(false, "Not implemented yet\n");
E1AP_E1AP_PDU_t pdu = {0};
return e1ap_encode_send(isCu, assoc_id, &pdu, 0, __func__);
E1AP_E1AP_PDU_t *pdu = calloc_or_fail(1, sizeof(*pdu));
return e1ap_encode_send(isCu, assoc_id, pdu, 0, __func__);
}
int e1ap_send_RESET_ACKNOWLEDGE(instance_t instance, E1AP_Reset_t *Reset)
@@ -304,9 +304,9 @@ static void fill_CONFIGURATION_UPDATE(E1AP_E1AP_PDU_t *pdu)
void e1apCUUP_send_CONFIGURATION_UPDATE(sctp_assoc_t assoc_id)
{
E1AP_E1AP_PDU_t pdu = {0};
fill_CONFIGURATION_UPDATE(&pdu);
e1ap_encode_send(UPtype, assoc_id, &pdu, 0, __func__);
E1AP_E1AP_PDU_t *pdu = calloc_or_fail(1, sizeof(*pdu));
fill_CONFIGURATION_UPDATE(pdu);
e1ap_encode_send(UPtype, assoc_id, pdu, 0, __func__);
}
int e1apCUCP_send_gNB_DU_CONFIGURATION_FAILURE(void)
+1 -1
View File
@@ -226,7 +226,7 @@ int e1ap_encode_send(E1_t type, sctp_assoc_t assoc_id, E1AP_E1AP_PDU_t *pdu, uin
void *buffer = NULL;
ssize_t encoded = aper_encode_to_new_buffer(&asn_DEF_E1AP_E1AP_PDU, 0, pdu, &buffer);
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_E1AP_E1AP_PDU, pdu);
ASN_STRUCT_FREE(asn_DEF_E1AP_E1AP_PDU, pdu);
if (encoded < 0) {
LOG_E(E1AP, "%s: Failed to encode E1AP message\n", func);
+1 -1
View File
@@ -153,7 +153,7 @@ static void e1_add_bearers(const int ue_id, const NR_DRB_ToAddModList_t *addMod,
// get SDAP config
sdap_config_t sdap = nr_sdap_get_config(GNB_FLAG_YES, drb->cnAssociation->choice.sdap_Config, drb->drb_Identity);
// add SDAP entity
nr_sdap_addmod_entity(GNB_FLAG_YES, ue_id, sdap);
nr_sdap_addmod_entity(GNB_FLAG_YES, ue_id, &sdap);
// add PDCP entity
nr_pdcp_add_drb(GNB_FLAG_YES, ue_id, drb->pdcp_Config, &sdap, sp);
}
+1 -1
View File
@@ -177,7 +177,7 @@ static void rrc_nsa_add_drb(const int ue_id,
sdap.drb_id = drb->drb_Identity;
sdap.defaultDRB = true;
// add SDAP entity (terminated at gNB, since it's EPC)
nr_sdap_addmod_entity(GNB_FLAG_YES, ue_id, sdap);
nr_sdap_addmod_entity(GNB_FLAG_YES, ue_id, &sdap);
// add PDCP entity
nr_pdcp_add_drb(GNB_FLAG_YES, ue_id, drb->pdcp_Config, &sdap, sp);
}
+1 -1
View File
@@ -619,7 +619,7 @@ static void rrc_ue_add_bearer(const int ue_id, const NR_DRB_ToAddMod_t *drb, con
sdap = nr_sdap_get_config(GNB_FLAG_NO, drb->cnAssociation->choice.sdap_Config, drb->drb_Identity);
}
// add SDAP entity
nr_sdap_addmod_entity(GNB_FLAG_NO, ue_id, sdap);
nr_sdap_addmod_entity(GNB_FLAG_NO, ue_id, &sdap);
// add PDCP entity
nr_pdcp_add_drb(GNB_FLAG_NO, ue_id, drb->pdcp_Config, &sdap, sp);
}
+11 -11
View File
@@ -210,8 +210,8 @@ static void nr_sdap_rx_entity(nr_sdap_entity_t *entity,
{
/* The offset of the SDAP header, it might be 0 if has_sdap_rx is not true in the pdcp entity. */
int offset=0;
int qfi = buf[0] & 0x3F; // QFI is always the first 6 bits in the first octet
if (qfi <= 0 || qfi >= SDAP_MAX_QFI) {
uint8_t qfi = buf[0] & 0x3F; // QFI is always the first 6 bits in the first octet
if (qfi >= SDAP_MAX_QFI) {
LOG_E(SDAP, "Invalid QFI %d received in SDAP header\n", qfi);
return;
}
@@ -450,7 +450,7 @@ static void nr_sdap_add_qos_flows_to_drb(nr_sdap_entity_t *entity, const sdap_co
for (int i = 0; i < sdap->mappedQFIs2AddCount; i++) {
uint8_t qfi = sdap->mappedQFIs2Add[i];
LOG_D(SDAP, "Adding QFI to DRB mapping rules: %d mapped QFIs for DRB %d\n", sdap->mappedQFIs2AddCount, sdap->drb_id);
if (qfi < SDAP_MAX_QFI && qfi > SDAP_MAP_RULE_EMPTY && sdap->drb_id > 0 && sdap->drb_id <= MAX_DRBS_PER_UE) {
if (qfi < SDAP_MAX_QFI && sdap->drb_id > SDAP_MAP_RULE_EMPTY && sdap->drb_id <= MAX_DRBS_PER_UE) {
entity->qfi2drb_map_add(entity, qfi, sdap->drb_id, sdap->role);
} else {
LOG_E(SDAP, "Failed to add qfi2drb mapping: QFI=%d, DRB=%d\n", qfi, sdap->drb_id);
@@ -487,13 +487,13 @@ static void nr_sdap_qfi2drb_map_update(nr_sdap_entity_t *entity, const sdap_conf
* @param is_gnb, indicates whether it is for gNB or UE
* @param ue_id, UE ID
* @param sdap, SDAP configuration */
static void nr_sdap_add_entity(const int is_gnb, const ue_id_t ue_id, const sdap_config_t sdap)
static void nr_sdap_add_entity(const int is_gnb, const ue_id_t ue_id, const sdap_config_t *sdap)
{
nr_sdap_entity_t *sdap_entity = calloc_or_fail(1, sizeof(*sdap_entity));
// SDAP entity ids
sdap_entity->ue_id = ue_id;
sdap_entity->pdusession_id = sdap.pdusession_id;
sdap_entity->pdusession_id = sdap->pdusession_id;
sdap_entity->is_gnb = is_gnb;
// rx/tx entities
@@ -513,13 +513,13 @@ static void nr_sdap_add_entity(const int is_gnb, const ue_id_t ue_id, const sdap
sdap_entity->pdusession_sock = -1;
// set default DRB
if (sdap.defaultDRB) {
sdap_entity->default_drb = sdap.drb_id;
if (sdap->defaultDRB) {
sdap_entity->default_drb = sdap->drb_id;
LOG_I(SDAP, "Default DRB for the created SDAP entity: DRB %d \n", sdap_entity->default_drb);
}
// Add QoS flows to the DRB (initial configuration)
nr_sdap_add_qos_flows_to_drb(sdap_entity, &sdap);
nr_sdap_add_qos_flows_to_drb(sdap_entity, sdap);
// update SDAP entity list pointers
sdap_entity->next_entity = sdap_info.sdap_entity_llist;
@@ -533,11 +533,11 @@ static void nr_sdap_add_entity(const int is_gnb, const ue_id_t ue_id, const sdap
}
/** @brief Add or modify an SDAP entity if it already exists */
void nr_sdap_addmod_entity(const int is_gnb, const ue_id_t ue_id, const sdap_config_t sdap)
void nr_sdap_addmod_entity(const int is_gnb, const ue_id_t ue_id, const sdap_config_t *sdap)
{
nr_sdap_entity_t *sdap_entity = nr_sdap_get_entity(ue_id, sdap.pdusession_id);
nr_sdap_entity_t *sdap_entity = nr_sdap_get_entity(ue_id, sdap->pdusession_id);
if (sdap_entity) {
sdap_entity->qfi2drb_map_update(sdap_entity, &sdap);
sdap_entity->qfi2drb_map_update(sdap_entity, sdap);
} else {
nr_sdap_add_entity(is_gnb, ue_id, sdap);
}
+1 -1
View File
@@ -172,7 +172,7 @@ sdap_config_t nr_sdap_get_config(const int is_gnb, const NR_SDAP_Config_t *sdap_
void nr_sdap_release_drb(ue_id_t ue_id, int drb_id, int pdusession_id);
/** @brief Add or modify an SDAP entity based on whether it already exists */
void nr_sdap_addmod_entity(const int is_gnb, const ue_id_t ue_id, const sdap_config_t sdap);
void nr_sdap_addmod_entity(const int is_gnb, const ue_id_t ue_id, const sdap_config_t *sdap);
/**
* @brief Function to delete a single SDAP Entity based on the ue_id and pdusession_id.
+1
View File
@@ -477,6 +477,7 @@ static void sctp_handle_new_association_req(const instance_t instance,
if (errno != EINPROGRESS) {
SCTP_ERROR("Connect failed: %s\n", strerror(errno));
sctp_itti_send_association_resp(requestor, instance, -1, req->ulp_cnx_id, SCTP_STATE_UNREACHABLE, 0, 0);
freeaddrinfo(serv);
close(sd);
return;
} else {
+22 -8
View File
@@ -152,6 +152,7 @@ class gtpEndPoint {
}
};
static void gtpv1uReceiverCancel(pthread_t t);
class gtpEndPoints {
public:
pthread_mutex_t gtp_lock = PTHREAD_MUTEX_INITIALIZER;
@@ -168,8 +169,10 @@ class gtpEndPoints {
~gtpEndPoints()
{
// automatically close all sockets on quit
for (const auto &p : instances)
for (const auto &p : instances) {
gtpv1uReceiverCancel(p.second.thrData.t);
close(p.first);
}
}
};
@@ -1283,7 +1286,7 @@ static int Gtpv1uHandleGpdu(int h, uint8_t *msgBuf, uint32_t msgBufLen, const st
return !GTPNOK;
}
static void gtpv1uReceiveHandleMessage(int h)
static bool gtpv1uReceiveHandleMessage(int h)
{
uint8_t udpData[65536];
int udpDataLen;
@@ -1293,19 +1296,19 @@ static void gtpv1uReceiveHandleMessage(int h)
if ((udpDataLen = recvfrom(h, udpData, sizeof(udpData), 0, (struct sockaddr *)&addr, &from_len)) < 0) {
LOG_E(GTPU, "[%d] Recvfrom failed (%s)\n", h, strerror(errno));
return;
return false;
} else if (udpDataLen == 0) {
LOG_W(GTPU, "[%d] Recvfrom returned 0\n", h);
return;
return true;
} else {
if (udpDataLen < (int)sizeof(Gtpv1uMsgHeaderT)) {
LOG_W(GTPU, "[%d] received malformed gtp packet \n", h);
return;
return true;
}
Gtpv1uMsgHeaderT *msg = (Gtpv1uMsgHeaderT *)udpData;
if ((int)(ntohs(msg->msgLength) + sizeof(Gtpv1uMsgHeaderT)) != udpDataLen) {
LOG_W(GTPU, "[%d] received malformed gtp packet length\n", h);
return;
return true;
}
LOG_D(GTPU, "[%d] Received GTP data, msg type: %x\n", h, msg->msgType);
switch (msg->msgType) {
@@ -1337,18 +1340,27 @@ static void gtpv1uReceiveHandleMessage(int h)
break;
}
}
return true;
}
static void* gtpv1uReceiver(void *thr)
{
gtpThread_t *gt = (gtpThread_t *)thr;
while (true) {
gtpv1uReceiveHandleMessage(gt->h);
while (gtpv1uReceiveHandleMessage(gt->h)) {
}
LOG_W(GTPU, "exiting thread\n");
return NULL;
}
static void gtpv1uReceiverCancel(pthread_t t)
{
int rc;
rc = pthread_cancel(t);
DevAssert(rc == 0);
rc = pthread_join(t, NULL);
DevAssert(rc == 0);
}
#include <openair2/ENB_APP/enb_paramdef.h>
void *gtpv1uTask(void *args)
@@ -1370,6 +1382,8 @@ void *gtpv1uTask(void *args)
// DATA TO BE SENT TO UDP
case TERMINATE_MESSAGE:
LOG_W(GTPU, "Exiting GTP instance %ld\n", myInstance);
itti_exit_task();
break;
case TIMER_HAS_EXPIRED:
+10
View File
@@ -1,4 +1,14 @@
add_executable(nr-cuup-load-test nr-cuup-load-test.c)
add_dependencies(nr-cuup-load-test params_libconfig)
target_link_libraries(nr-cuup-load-test PRIVATE GTPV1U ${T_LIB} utils ITTI e1ap
SCTP_CLIENT)
target_link_libraries(nr-cuup-load-test PRIVATE asn1_lte_rrc_hdrs asn1_nr_rrc_hdrs) # ITTI
if(ENABLE_TESTS)
# we add also nr-cuup as it is required for the test
add_dependencies(tests nr-cuup-load-test nr-cuup)
add_test(NAME nr_cuup_functional_test
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/nr-cuup-functional-test.sh ${CMAKE_CURRENT_SOURCE_DIR}/load-test.conf
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
endif()
+21
View File
@@ -0,0 +1,21 @@
#!/bin/bash
# this script takes one argument, which is the path to the CU-UP configuration
if [ $# -ne 1 ]; then
echo "usage: $0 <cuup-config>"
exit 1
fi
CONFIG=$1
set -x
# current dir should be CMAKE_BINARY_DIR
./nr-cuup -O ${CONFIG} &
CUUP_PID=$!
timeout 5s ./tests/nr-cuup/nr-cuup-load-test -t 3 -d 10 -u 10
RET=$?
kill ${CUUP_PID}
exit ${RET}
+5
View File
@@ -28,6 +28,7 @@
#include "openair3/SCTP/sctp_eNB_task.h"
#include "openair2/E1AP/e1ap.h"
#include "openair3/ocp-gtpu/gtp_itf.h"
#include "openair2/E1AP/lib/e1ap_interface_management.h"
configmodule_interface_t *uniqCfg;
@@ -90,6 +91,7 @@ static void setup_cuup(sctp_assoc_t *assoc_id, e1ap_nssai_t *nssai)
DevAssert(req->plmn[0].slice != NULL);
*nssai = *req->plmn[0].slice;
DevAssert(assoc_id != 0);
free_e1ap_cuup_setup_request(req);
itti_free(TASK_GNB_APP, itti_req);
// acknowledge the E1 setup request with a response
@@ -664,6 +666,9 @@ int main(int argc, char *argv[])
success = false;
}
free(ng_ip);
free(f1_ip);
if (success)
LOG_A(GNB_APP, "test succeeded\n");
else