Merge remote-tracking branch 'origin/cuup-load-tester' into integration_2025_w32 (!3547)

Add a CU-UP load tester and improve GTP performance

This adds a simple tester program for the CU-UP. It sets up a number of
UEs/bearers in the CU-UP via the E1 interface, then streams data in
downlink/uplink (as seen by the CU-UP). More information is available in
tests/nr-cuup/README.md.

Includes some fixes to properly verify that SCTP is installed, and use
"the right cmake way" to use it in cmake code (see commit for details).
This commit is contained in:
Jaroslava Fiedlerova
2025-08-04 23:59:05 +02:00
37 changed files with 1431 additions and 1016 deletions
+9 -5
View File
@@ -36,6 +36,8 @@ if(NOT DEFINED ENV{CPM_SOURCE_CACHE})
endif()
include("cmake_targets/CPM.cmake")
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake_targets/tools/MODULES" "${CMAKE_MODULE_PATH}")
##############################
### CCache: reduce compilation time
##############################
@@ -1407,12 +1409,14 @@ add_library(MME_APP ${MME_APP_SRC})
target_link_libraries(MME_APP PRIVATE m2ap m3ap)
target_link_libraries(MME_APP PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs)
find_package(sctp REQUIRED)
set(SCTP_SRC
${OPENAIR3_DIR}/SCTP/sctp_common.c
${OPENAIR3_DIR}/SCTP/sctp_eNB_task.c
${OPENAIR3_DIR}/SCTP/sctp_eNB_itti_messaging.c
)
add_library(SCTP_CLIENT ${SCTP_SRC})
target_link_libraries(SCTP_CLIENT PRIVATE sctp::sctp)
target_link_libraries(SCTP_CLIENT PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs)
set(NAS_SRC ${OPENAIR3_DIR}/NAS/)
@@ -1711,7 +1715,6 @@ include_directories("${NFAPI_DIR}/pnf_sim/inc")
add_library(oai_iqplayer MODULE
${OPENAIR_DIR}/radio/iqplayer/iqplayer_lib.c
)
set(CMAKE_MODULE_PATH "${OPENAIR_DIR}/cmake_targets/tools/MODULES" "${CMAKE_MODULE_PATH}")
#################################
# add executables for operation
@@ -1758,7 +1761,7 @@ target_link_libraries(lte-softmodem PRIVATE
${NAS_UE_LIB} ITTI SIMU shlib_loader
-Wl,--end-group z dl)
target_link_libraries(lte-softmodem PRIVATE pthread m CONFIG_LIB rt sctp)
target_link_libraries(lte-softmodem PRIVATE pthread m CONFIG_LIB rt)
target_link_libraries(lte-softmodem PRIVATE ${T_LIB})
target_link_libraries(lte-softmodem PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs)
target_link_libraries(lte-softmodem PRIVATE ${blas_LIBRARIES} ${cblas_LIBRARIES} ${lapacke_LIBRARIES} ${lapack_LIBRARIES})
@@ -1814,7 +1817,7 @@ target_link_libraries(lte-uesoftmodem PRIVATE
${NAS_UE_LIB} ITTI shlib_loader
-Wl,--end-group z dl)
target_link_libraries(lte-uesoftmodem PRIVATE pthread m CONFIG_LIB rt sctp)
target_link_libraries(lte-uesoftmodem PRIVATE pthread m CONFIG_LIB rt)
target_link_libraries(lte-uesoftmodem PRIVATE ${T_LIB})
target_link_libraries(lte-uesoftmodem PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs)
target_link_libraries(lte-uesoftmodem PRIVATE ${blas_LIBRARIES} ${cblas_LIBRARIES} ${lapacke_LIBRARIES} ${lapack_LIBRARIES})
@@ -1851,7 +1854,7 @@ target_link_libraries(nr-softmodem PRIVATE
time_management
-Wl,--end-group z dl)
target_link_libraries(nr-softmodem PRIVATE pthread m CONFIG_LIB rt sctp)
target_link_libraries(nr-softmodem PRIVATE pthread m CONFIG_LIB rt)
target_link_libraries(nr-softmodem PRIVATE ${T_LIB})
target_link_libraries(nr-softmodem PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs)
target_link_libraries(nr-softmodem PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs)
@@ -1888,7 +1891,7 @@ target_link_libraries(nr-cuup PRIVATE
CONFIG_LIB ITTI SCTP_CLIENT
GTPV1U e1ap f1ap
time_management
z sctp dl pthread shlib_loader ${T_LIB})
z dl pthread shlib_loader ${T_LIB})
target_link_libraries(nr-cuup PRIVATE asn1_lte_rrc_hdrs asn1_nr_rrc_hdrs)
if(E2_AGENT)
target_link_libraries(nr-cuup PRIVATE e2_agent e2_agent_arg e2_ran_func_cuup)
@@ -2182,3 +2185,4 @@ add_subdirectory(openair1)
add_subdirectory(openair2)
add_subdirectory(openair3)
add_subdirectory(radio)
add_subdirectory(tests)
@@ -23,9 +23,7 @@ gNBs =
local_s_address = "192.168.71.150";
remote_s_address = "192.168.71.171";
local_s_portc = 501;
local_s_portd = 2153;
remote_s_portc = 500;
remote_s_portd = 2153;
# ------- SCTP definitions
-2
View File
@@ -23,9 +23,7 @@ gNBs =
local_s_address = "127.0.0.4";
remote_s_address = "127.0.0.5";
local_s_portc = 501;
local_s_portd = 2153;
remote_s_portc = 500;
remote_s_portd = 2153;
# ------- SCTP definitions
@@ -23,9 +23,7 @@ gNBs =
local_s_address = "192.168.71.150";
remote_s_address = "0.0.0.0"; # multiple DUs
local_s_portc = 501;
local_s_portd = 2152;
remote_s_portc = 500;
remote_s_portd = 2152;
# ------- SCTP definitions
@@ -23,9 +23,7 @@ gNBs =
local_s_address = "127.0.0.4";
remote_s_address = "127.0.0.5";
local_s_portc = 501;
local_s_portd = 2153;
remote_s_portc = 500;
remote_s_portd = 2153;
# ------- SCTP definitions
@@ -22,9 +22,7 @@ gNBs =
local_s_address = "192.168.72.161";
remote_s_address = "192.168.72.171";
local_s_portc = 501;
local_s_portd = 2153;
remote_s_portc = 500;
remote_s_portd = 2153;
# ------- SCTP definitions
@@ -22,9 +22,7 @@ gNBs =
local_s_address = "127.0.0.6";
remote_s_address = "127.0.0.5";
local_s_portc = 501;
local_s_portd = 2153;
remote_s_portc = 500;
remote_s_portd = 2153;
# ------- SCTP definitions
@@ -163,9 +163,7 @@ MACRLCs = (
tr_n_preference = "f1";
local_n_address = "127.0.0.5";
remote_n_address = "127.0.0.4";
local_n_portc = 500;
local_n_portd = 2153;
remote_n_portc = 501;
remote_n_portd = 2153;
pusch_TargetSNRx10 = 200;
pucch_TargetSNRx10 = 200;
@@ -168,9 +168,7 @@ MACRLCs = (
tr_n_preference = "f1";
local_n_address = "192.168.71.171";
remote_n_address = "192.168.71.150";
local_n_portc = 500;
local_n_portd = 2153;
remote_n_portc = 501;
remote_n_portd = 2153;
pusch_TargetSNRx10 = 200;
pucch_TargetSNRx10 = 200;
@@ -204,9 +204,7 @@ MACRLCs = (
tr_n_preference = "f1";
local_n_address = "127.0.0.5";
remote_n_address = "127.0.0.4";
local_n_portc = 500;
local_n_portd = 2153;
remote_n_portc = 501;
remote_n_portd = 2153;
pusch_TargetSNRx10 = 200;
pucch_TargetSNRx10 = 200;
@@ -1,46 +0,0 @@
# - Try to find SCTP library and headers
# Once done, this will define
#
# SCTP_FOUND - system has SCTP
# SCTP_INCLUDE_DIR - the SCTP include directories
# SCTP_LIBRARIES - link these to use SCTP
if (SCTP_INCLUDE_DIR AND SCTP_LIBRARIES)
set(SCTP_FIND_QUIETLY TRUE)
endif (SCTP_INCLUDE_DIR AND SCTP_LIBRARIES)
# Include dir
find_path(SCTP_INCLUDE_DIR
NAMES netinet/sctp.h
)
# Library
find_library(SCTP_LIBRARY
NAMES sctp
)
# Set the include dir variables and the libraries and let libfind_process do the rest.
# NOTE: Singular variables for this library, plural for libraries this this lib depends on.
#set(SCTP_PROCESS_INCLUDES SCTP_INCLUDE_DIR)
#set(SCTP_PROCESS_LIBS SCTP_LIBRARY)
#libfind_process(SCTP)
# handle the QUIETLY and REQUIRED arguments and set SCTP_FOUND to TRUE if
# all listed variables are TRUE
INCLUDE(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(SCTP DEFAULT_MSG SCTP_LIBRARY SCTP_INCLUDE_DIR)
# If we successfully found the sctp library then add the library to the
# SCTP_LIBRARIES cmake variable otherwise set SCTP_LIBRARIES to nothing.
IF(SCTP_FOUND)
SET( SCTP_LIBRARIES ${SCTP_LIBRARY} )
ELSE(SCTP_FOUND)
SET( SCTP_LIBRARIES )
ENDIF(SCTP_FOUND)
# Lastly make it so that the SCTP_LIBRARY and SCTP_INCLUDE_DIR variables
# only show up under the advanced options in the gui cmake applications.
MARK_AS_ADVANCED( SCTP_LIBRARY SCTP_INCLUDE_DIR )
@@ -0,0 +1,64 @@
# - Try to find SCTP library and headers
#
# Imported Targets
# ^^^^^^^^^^^^^^^^
#
# This module provides the following imported targets, if found:
#
# ``sctp::sctp``
# The SCTP library
#
# Result Variables
# ^^^^^^^^^^^^^^^^
#
# this will define
#
# sctp_FOUND - system has SCTP
# sctp_INCLUDE_DIRS - the SCTP include directories
# sctp_LIBRARIES - link these to use SCTP
# Include dir
find_path(sctp_INCLUDE_DIR
NAMES netinet/sctp.h
)
# Library
find_library(sctp_LIBRARY
NAMES sctp
)
# Set the include dir variables and the libraries and let libfind_process do the rest.
# NOTE: Singular variables for this library, plural for libraries this this lib depends on.
#set(sctp_PROCESS_INCLUDES sctp_INCLUDE_DIR)
#set(sctp_PROCESS_LIBS sctp_LIBRARY)
#libfind_process(sctp)
# handle the QUIETLY and REQUIRED arguments and set sctp_FOUND to TRUE if
# all listed variables are TRUE
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(sctp
FOUND_VAR sctp_FOUND
REQUIRED_VARS
sctp_LIBRARY
sctp_INCLUDE_DIR
)
# If we successfully found the sctp library then add the library to the
# sctp_LIBRARIES cmake variable otherwise set sctp_LIBRARIES to nothing.
if(sctp_FOUND)
set(sctp_LIBRARIES ${sctp_LIBRARY})
set(sctp_INCLUDE_DIRS ${sctp_INCLUDE_DIR})
endif()
if(sctp_FOUND AND NOT TARGET sctp::sctp)
add_library(sctp::sctp UNKNOWN IMPORTED)
set_target_properties(sctp::sctp PROPERTIES
IMPORTED_LOCATION "${sctp_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${sctp_INCLUDE_DIRS}"
)
endif()
# Lastly make it so that the SCTP_LIBRARY and SCTP_INCLUDE_DIR variables
# only show up under the advanced options in the gui cmake applications.
mark_as_advanced(SCTP_LIBRARY SCTP_INCLUDE_DIR)
-3
View File
@@ -46,7 +46,6 @@ struct gNB_RRC_INST_s;
struct eNB_MAC_INST_s;
struct eNB_MAC_INST_NB_IoT_s;
struct gNB_MAC_INST_s;
struct gtpv1u_data_s;
struct RU_t_s;
typedef struct {
@@ -100,8 +99,6 @@ typedef struct {
struct eNB_MAC_INST_NB_IoT_s **nb_iot_mac;
/// NR MAC context variables
struct gNB_MAC_INST_s **nrmac;
/// GTPu descriptor
struct gtpv1u_data_s *gtpv1u_data_g;
/// RU descriptors. These describe what each radio unit is supposed to do and contain the necessary functions for fronthaul interfaces
struct RU_t_s **ru;
/// Mask to indicate fronthaul setup status of RU (hard-limit to 64 RUs)
-2
View File
@@ -221,9 +221,7 @@ In the CU file:
ignored, but we recommend to put `0.0.0.0` ("any")
- Ports should match the ones in the DU config, but for simplicity and
standards-conformity, simply set all to 2152:
- `local_s_portc` in CU should match `remote_n_portc` in DU
- `local_s_portd` in CU should match `remote_n_portd` in DU
- `remote_s_portc` in CU should match `local_n_portc` in DU
- `remote_s_portd` in CU should match `local_n_portd` in DU
In the DU file:
+2
View File
@@ -130,6 +130,8 @@ The other SDRs (AW2S, LimeSDR, ...) have no READMEs.
# Testing
- [UnitTests.md](./UnitTests.md) explains the unit testing setup
- Component tests are under `tests/`. Currently, there is a simple CU-UP
tester, see the corresponding [README.md](../tests/nr-cuup/README.md).
- [TESTBenches.md](./TESTBenches.md) lists the CI setup and links to pipelines
# Developer tools
-1
View File
@@ -25,4 +25,3 @@ MESSAGE_DEF(GTPV1U_ENB_DATA_FORWARDING_IND, MESSAGE_PRIORITY_MED, gtpv1u_enb_dat
MESSAGE_DEF(GTPV1U_ENB_END_MARKER_REQ, MESSAGE_PRIORITY_MED, gtpv1u_enb_end_marker_req_t, Gtpv1uEndMarkerReq)
MESSAGE_DEF(GTPV1U_ENB_END_MARKER_IND, MESSAGE_PRIORITY_MED, gtpv1u_enb_end_marker_ind_t, Gtpv1uEndMarkerInd)
MESSAGE_DEF(GTPV1U_REQ, MESSAGE_PRIORITY_MED, Gtpv1uReq, gtpv1uReq)
MESSAGE_DEF(GTPV1U_DU_BUFFER_REPORT_REQ, MESSAGE_PRIORITY_MED, gtpv1u_DU_buffer_report_req_t, NRGtpv1uBufferReportReq)
-9
View File
@@ -37,8 +37,6 @@
#define GTPV1U_REQ(mSGpTR) (mSGpTR)->ittiMsg.gtpv1uReq
#define GTPV1U_DU_BUFFER_REPORT_REQ(mSGpTR) (mSGpTR)->ittiMsg.NRGtpv1uBufferReportReq
#define GTPV1U_ALL_TUNNELS_TEID (teid_t)0xFFFFFFFF
typedef struct gtpv1u_enb_create_x2u_tunnel_req_s {
@@ -188,11 +186,4 @@ typedef struct gtpv1u_gnb_delete_tunnel_resp_s {
teid_t gnb_NGu_teid; ///< local NGU Tunnel Endpoint Identifier to be deleted
} gtpv1u_gnb_delete_tunnel_resp_t;
typedef struct gtpv1u_DU_buffer_report_req_s {
uint32_t buffer_availability;
ue_id_t ue_id;
int pdusession_id;
} gtpv1u_DU_buffer_report_req_t;
#endif /* GTPV1_U_MESSAGES_TYPES_H_ */
-37
View File
@@ -7721,9 +7721,6 @@ void *rrc_enb_process_itti_msg(void *notUsed) {
case X2AP_HANDOVER_REQ_ACK: {
struct rrc_eNB_ue_context_s *ue_context_p = NULL;
x2ap_handover_req_ack_t *x2ap_handover_req_ack = NULL;
hashtable_rc_t hash_rc = HASH_TABLE_KEY_NOT_EXISTS;
gtpv1u_ue_data_t *gtpv1u_ue_data_p = NULL;
ue_context_p = rrc_eNB_get_ue_context(RC.rrc[instance], X2AP_HANDOVER_REQ_ACK(msg_p).rnti);
if (ue_context_p == NULL) {
@@ -7738,40 +7735,6 @@ void *rrc_enb_process_itti_msg(void *notUsed) {
if (ue_context_p->ue_context.handover_info->state != HO_REQUEST)
abort();
hash_rc = hashtable_get(RC.gtpv1u_data_g->ue_mapping, ue_context_p->ue_context.rnti, (void **)&gtpv1u_ue_data_p);
/* set target enb gtp teid */
if (hash_rc == HASH_TABLE_KEY_NOT_EXISTS) {
LOG_E(RRC, "X2AP_HANDOVER_REQ_ACK func(), hashtable_get failed: while getting ue rnti %x in hashtable ue_mapping\n", ue_context_p->ue_context.rnti);
} else {
uint8_t nb_e_rabs_tobesetup = 0;
ebi_t eps_bearer_id = 0;
int ip_offset = 0;
in_addr_t in_addr;
x2ap_handover_req_ack = &X2AP_HANDOVER_REQ_ACK(msg_p);
nb_e_rabs_tobesetup = x2ap_handover_req_ack->nb_e_rabs_tobesetup;
ue_context_p->ue_context.nb_x2u_e_rabs = nb_e_rabs_tobesetup;
for (int i = 0; i < nb_e_rabs_tobesetup; i++) {
ip_offset = 0;
eps_bearer_id = x2ap_handover_req_ack->e_rabs_tobesetup[i].e_rab_id;
ue_context_p->ue_context.enb_gtp_x2u_ebi[i] = eps_bearer_id;
ue_context_p->ue_context.enb_gtp_x2u_teid[i] = x2ap_handover_req_ack->e_rabs_tobesetup[i].gtp_teid;
gtpv1u_ue_data_p->bearers[eps_bearer_id - GTPV1U_BEARER_OFFSET].teid_teNB = x2ap_handover_req_ack->e_rabs_tobesetup[i].gtp_teid;
if ((x2ap_handover_req_ack->e_rabs_tobesetup[i].eNB_addr.length == 4) || (x2ap_handover_req_ack->e_rabs_tobesetup[i].eNB_addr.length == 20)) {
in_addr = *((in_addr_t *)x2ap_handover_req_ack->e_rabs_tobesetup[i].eNB_addr.buffer);
ip_offset = 4;
gtpv1u_ue_data_p->bearers[eps_bearer_id - GTPV1U_BEARER_OFFSET].tenb_ip_addr = in_addr;
ue_context_p->ue_context.enb_gtp_x2u_addrs[i] = x2ap_handover_req_ack->e_rabs_tobesetup[i].eNB_addr;
}
if ((x2ap_handover_req_ack->e_rabs_tobesetup[i].eNB_addr.length == 16) || (x2ap_handover_req_ack->e_rabs_tobesetup[i].eNB_addr.length == 20)) {
memcpy(gtpv1u_ue_data_p->bearers[eps_bearer_id - GTPV1U_BEARER_OFFSET].tenb_ip6_addr.s6_addr, &x2ap_handover_req_ack->e_rabs_tobesetup[i].eNB_addr.buffer[ip_offset], 16);
}
}
}
rrc_eNB_process_handoverCommand(instance, ue_context_p, &X2AP_HANDOVER_REQ_ACK(msg_p));
ue_context_p->ue_context.handover_info->state = HO_PREPARE;
break;
File diff suppressed because it is too large Load Diff
+27 -10
View File
@@ -1,16 +1,40 @@
#ifndef __GTPUNEW_ITF_H__
#define __GTPUNEW_ITF_H__
#include <stdint.h>
#include <limits.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define GTPNOK -1
# define GTPU_HEADER_OVERHEAD_MAX 64
#include "openair2/COMMON/gtpv1_u_messages_types.h"
#include "common/utils/hashtable/hashtable.h"
#include "common/platform_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/* forward declaration */
struct protocol_ctxt_s;
typedef struct protocol_ctxt_s protocol_ctxt_t;
struct gtpv1u_enb_create_tunnel_req_s;
typedef struct gtpv1u_enb_create_tunnel_req_s gtpv1u_enb_create_tunnel_req_t;
struct gtpv1u_enb_create_tunnel_resp_s;
typedef struct gtpv1u_enb_create_tunnel_resp_s gtpv1u_enb_create_tunnel_resp_t;
struct gtpv1u_enb_delete_tunnel_req_s;
typedef struct gtpv1u_enb_delete_tunnel_req_s gtpv1u_enb_delete_tunnel_req_t;
struct gtpv1u_enb_create_x2u_tunnel_req_s;
typedef struct gtpv1u_enb_create_x2u_tunnel_req_s gtpv1u_enb_create_x2u_tunnel_req_t;
struct gtpv1u_enb_create_x2u_tunnel_resp_s;
typedef struct gtpv1u_enb_create_x2u_tunnel_resp_s gtpv1u_enb_create_x2u_tunnel_resp_t;
struct gtpv1u_gnb_create_tunnel_req_s;
typedef struct gtpv1u_gnb_create_tunnel_req_s gtpv1u_gnb_create_tunnel_req_t;
struct gtpv1u_gnb_create_tunnel_resp_s;
typedef struct gtpv1u_gnb_create_tunnel_resp_s gtpv1u_gnb_create_tunnel_resp_t;
struct gtpv1u_gnb_delete_tunnel_req_s;
typedef struct gtpv1u_gnb_delete_tunnel_req_s gtpv1u_gnb_delete_tunnel_req_t;
typedef bool (*gtpCallback)(protocol_ctxt_t *ctxt_pP,
const srb_flag_t srb_flagP,
const rb_id_t rb_idP,
@@ -52,7 +76,6 @@ extern "C" {
// the init function create a gtp instance and return the gtp instance id
// the parameter originInstance will be sent back in each message from gtp to the creator
void gtpv1uReceiver(int h);
void gtpv1uProcessTimeout(int handle,void *arg);
int gtpv1u_create_s1u_tunnel(const instance_t instance,
const gtpv1u_enb_create_tunnel_req_t *create_tunnel_req,
@@ -91,7 +114,6 @@ extern "C" {
teid_t teid,
int outgoing_qfi,
transport_layer_addr_t remoteAddr,
int port,
gtpCallback callBack,
gtpCallbackSDAP callBackSDAP);
@@ -110,13 +132,8 @@ extern "C" {
instance_t gtpv1Init(openAddr_t context);
void *gtpv1uTask(void *args);
// Legacy to fix
typedef struct gtpv1u_data_s {
/* RB tree of UEs */
hash_table_t *ue_mapping;
} gtpv1u_data_t;
#define GTPV1U_BEARER_OFFSET 3
#define GTPV1U_MAX_BEARERS_ID (max_val_LTE_DRB_Identity - GTPV1U_BEARER_OFFSET)
#define GTPV1U_MAX_BEARERS_ID (32 - GTPV1U_BEARER_OFFSET) /* max_val_LTE_DRB_Identity */
typedef enum {
BEARER_DOWN = 0,
BEARER_IN_CONFIG,
@@ -1,196 +0,0 @@
Active_eNBs = ( "eNB-CU-Eurecom-LTEBox");
# Asn1_verbosity, choice in: none, info, annoying
Asn1_verbosity = "none";
eNBs = (
{
////////// Identification parameters:
eNB_ID = 0xe00;
cell_type = "CELL_MACRO_ENB";
eNB_name = "eNB-CU-Eurecom-LTEBox";
// Tracking area code, 0x0000 and 0xfffe are reserved values
tracking_area_code = 1;
plmn_list = ( { mcc = 208; mnc = 95; mnc_length = 2; } )
nr_cellid = 12345678L
tr_s_preference = "f1"
remote_s_address = "192.168.12.4";
local_s_address = "192.168.12.45";
local_s_portc = 501;
remote_s_portc = 500;
local_s_portd = 601;
remote_s_portd = 600;
////////// Physical parameters:
component_carriers = (
{
node_function = "3GPP_eNodeB";
node_timing = "synch_to_ext_device";
node_synch_ref = 0;
frame_type = "FDD";
tdd_config = 3;
tdd_config_s = 0;
prefix_type = "NORMAL";
eutra_band = 7;
downlink_frequency = 2665000000L;
uplink_frequency_offset = -120000000;
Nid_cell = 0;
N_RB_DL = 50;
pbch_repetition = "FALSE";
prach_root = 0;
prach_config_index = 0;
prach_high_speed = "DISABLE";
prach_zero_correlation = 1;
prach_freq_offset = 2;
pucch_delta_shift = 1;
pucch_nRB_CQI = 0;
pucch_nCS_AN = 0;
pucch_n1_AN = 0;
pdsch_referenceSignalPower = -27;
pdsch_p_b = 0;
pusch_n_SB = 1;
pusch_enable64QAM = "DISABLE";
pusch_hoppingMode = "interSubFrame";
pusch_hoppingOffset = 0;
pusch_groupHoppingEnable = "ENABLE";
pusch_groupAssignment = 0;
pusch_sequenceHoppingEnabled = "DISABLE";
pusch_nDMRS1 = 1;
phich_duration = "NORMAL";
phich_resource = "ONESIXTH";
srs_enable = "DISABLE";
/*
srs_BandwidthConfig =;
srs_SubframeConfig =;
srs_ackNackST =;
srs_MaxUpPts =;
*/
pusch_p0_Nominal = -96;
pusch_alpha = "AL1";
pucch_p0_Nominal = -104;
msg3_delta_Preamble = 6;
pucch_deltaF_Format1 = "deltaF2";
pucch_deltaF_Format1b = "deltaF3";
pucch_deltaF_Format2 = "deltaF0";
pucch_deltaF_Format2a = "deltaF0";
pucch_deltaF_Format2b = "deltaF0";
rach_numberOfRA_Preambles = 64;
rach_preamblesGroupAConfig = "DISABLE";
/*
rach_sizeOfRA_PreamblesGroupA = ;
rach_messageSizeGroupA = ;
rach_messagePowerOffsetGroupB = ;
*/
rach_powerRampingStep = 4;
rach_preambleInitialReceivedTargetPower = -108;
rach_preambleTransMax = 10;
rach_raResponseWindowSize = 10;
rach_macContentionResolutionTimer = 48;
rach_maxHARQ_Msg3Tx = 4;
pcch_default_PagingCycle = 128;
pcch_nB = "oneT";
bcch_modificationPeriodCoeff= 2;
ue_TimersAndConstants_t300 = 1000;
ue_TimersAndConstants_t301 = 1000;
ue_TimersAndConstants_t310 = 1000;
ue_TimersAndConstants_t311 = 10000;
ue_TimersAndConstants_n310 = 20;
ue_TimersAndConstants_n311 = 1;
ue_TransmissionMode = 1;
//Parameters for SIB18
rxPool_sc_CP_Len = "normal";
rxPool_sc_Period = "sf40";
rxPool_data_CP_Len = "normal";
rxPool_ResourceConfig_prb_Num = 20;
rxPool_ResourceConfig_prb_Start = 5;
rxPool_ResourceConfig_prb_End = 44;
rxPool_ResourceConfig_offsetIndicator_present = "prSmall";
rxPool_ResourceConfig_offsetIndicator_choice = 0;
rxPool_ResourceConfig_subframeBitmap_present = "prBs40";
rxPool_ResourceConfig_subframeBitmap_choice_bs_buf = "00000000000000000000";
rxPool_ResourceConfig_subframeBitmap_choice_bs_size = 5;
rxPool_ResourceConfig_subframeBitmap_choice_bs_bits_unused = 0;
/*
rxPool_dataHoppingConfig_hoppingParameter = 0;
rxPool_dataHoppingConfig_numSubbands = "ns1";
rxPool_dataHoppingConfig_rbOffset = 0;
rxPool_commTxResourceUC-ReqAllowed = "TRUE";
*/
// Parameters for SIB19
discRxPool_cp_Len = "normal"
discRxPool_discPeriod = "rf32"
discRxPool_numRetx = 1;
discRxPool_numRepetition = 2;
discRxPool_ResourceConfig_prb_Num = 5;
discRxPool_ResourceConfig_prb_Start = 3;
discRxPool_ResourceConfig_prb_End = 21;
discRxPool_ResourceConfig_offsetIndicator_present = "prSmall";
discRxPool_ResourceConfig_offsetIndicator_choice = 0;
discRxPool_ResourceConfig_subframeBitmap_present = "prBs40";
discRxPool_ResourceConfig_subframeBitmap_choice_bs_buf = "f0ffffffff";
discRxPool_ResourceConfig_subframeBitmap_choice_bs_size = 5;
discRxPool_ResourceConfig_subframeBitmap_choice_bs_bits_unused = 0;
}
);
srb1_parameters :
{
# timer_poll_retransmit = (ms) [5, 10, 15, 20,... 250, 300, 350, ... 500]
timer_poll_retransmit = 80;
# timer_reordering = (ms) [0,5, ... 100, 110, 120, ... ,200]
timer_reordering = 35;
# timer_reordering = (ms) [0,5, ... 250, 300, 350, ... ,500]
timer_status_prohibit = 0;
# poll_pdu = [4, 8, 16, 32 , 64, 128, 256, infinity(>10000)]
poll_pdu = 4;
# poll_byte = (kB) [25,50,75,100,125,250,375,500,750,1000,1250,1500,2000,3000,infinity(>10000)]
poll_byte = 99999;
# max_retx_threshold = [1, 2, 3, 4 , 6, 8, 16, 32]
max_retx_threshold = 4;
}
# ------- SCTP definitions
SCTP :
{
# Number of streams to use in input/output
SCTP_INSTREAMS = 2;
SCTP_OUTSTREAMS = 2;
};
////////// MME parameters:
mme_ip_address = ({ ipv4 = "127.0.1.10"; });
NETWORK_INTERFACES : {
ENB_IPV4_ADDRESS_FOR_S1_MME = "127.0.1.30/24";
ENB_IPV4_ADDRESS_FOR_S1U = "127.0.1.30/24";
ENB_PORT_FOR_S1U = 2152; # Spec 2152
ENB_IPV4_ADDRESS_FOR_X2C = "127.0.1.30/24";
ENB_PORT_FOR_X2C = 36422; # Spec 36422
};
}
);
log_config = {
global_log_level = "info";
pdcp_log_level = "info";
rrc_log_level = "info";
gtp_log_level = "info";
};
@@ -1,102 +0,0 @@
Active_eNBs = ( "eNB-Eurecom-DU");
# Asn1_verbosity, choice in: none, info, annoying
Asn1_verbosity = "none";
eNBs =
(
{
////////// Identification parameters:
eNB_CU_ID = 0xe00;
eNB_name = "eNB-Eurecom-DU";
// Tracking area code, 0x0000 and 0xfffe are reserved values
tracking_area_code = 1;
plmn_list = ( { mcc = 208; mnc = 95; mnc_length = 2; } )
nr_cellid = 12345678L
////////// Physical parameters:
component_carriers = (
{
node_function = "3GPP_eNODEB";
node_timing = "synch_to_ext_device";
node_synch_ref = 0;
frame_type = "FDD";
tdd_config = 3;
tdd_config_s = 0;
prefix_type = "NORMAL";
eutra_band = 7;
downlink_frequency = 2665000000L;
uplink_frequency_offset = -120000000;
Nid_cell = 0;
N_RB_DL = 50;
Nid_cell_mbsfn = 0;
nb_antenna_ports = 1;
nb_antennas_tx = 1;
nb_antennas_rx = 1;
tx_gain = 90;
rx_gain = 125;
pucch_deltaF_Format1 = "deltaF2";
pucch_deltaF_Format1b = "deltaF3";
pucch_deltaF_Format2 = "deltaF0";
pucch_deltaF_Format2a = "deltaF0";
pucch_deltaF_Format2b = "deltaF0";
}
);
# ------- SCTP definitions
SCTP :
{
# Number of streams to use in input/output
SCTP_INSTREAMS = 2;
SCTP_OUTSTREAMS = 2;
};
}
);
MACRLCs = (
{
num_cc = 1;
tr_s_preference = "local_L1";
tr_n_preference = "f1";
remote_n_address = "127.0.0.4";
local_n_address = "127.0.0.3";
local_n_portc = 500;
remote_n_portc = 501;
local_n_portd = 600;
remote_n_portd = 601;
}
);
L1s = (
{
num_cc = 1;
tr_n_preference = "local_mac";
}
);
RUs = (
{
local_rf = "yes";
nb_tx = 1;
nb_rx = 1;
att_tx = 10;
att_rx = 10;
bands = [7];
max_pdschReferenceSignalPower = -27;
max_rxgain = 125;
eNB_instances = [0];
}
);
log_config = {
global_log_level = "info";
hw_log_level = "info";
phy_log_level = "info";
mac_log_level = "info";
rlc_log_level = "info";
};
@@ -22,9 +22,7 @@ gNBs =
local_s_address = "127.0.0.4";
remote_s_address = "127.0.0.3";
local_s_portc = 501;
local_s_portd = 2152;
remote_s_portc = 500;
remote_s_portd = 2152;
# ------- SCTP definitions
@@ -168,9 +168,7 @@ MACRLCs = (
tr_n_preference = "f1";
local_n_address = "127.0.0.3";
remote_n_address = "127.0.0.4";
local_n_portc = 500;
local_n_portd = 2152;
remote_n_portc = 501;
remote_n_portd = 2152;
}
@@ -17,12 +17,9 @@ gNBs =
tr_s_preference = "f1";
local_s_if_name = "lo";
local_s_address = "127.0.0.3";
remote_s_address = "0.0.0.0"; # multiple DUs
local_s_portc = 501;
local_s_portd = 2152;
remote_s_portc = 500;
remote_s_portd = 2152;
@@ -163,12 +163,9 @@ MACRLCs = ({
num_cc = 1;
tr_s_preference = "local_L1";
tr_n_preference = "f1";
local_n_if_name = "lo";
local_n_address = "127.0.0.4";
remote_n_address = "127.0.0.3";
local_n_portc = 500;
local_n_portd = 2152;
remote_n_portc = 501;
remote_n_portd = 2152;
pusch_FailureThres = 1000;
});
@@ -163,12 +163,9 @@ MACRLCs = ({
num_cc = 1;
tr_s_preference = "local_L1";
tr_n_preference = "f1";
local_n_if_name = "lo";
local_n_address = "127.0.0.5";
remote_n_address = "127.0.0.3";
local_n_portc = 500;
local_n_portd = 2152;
remote_n_portc = 501;
remote_n_portd = 2152;
pusch_FailureThres = 1000;
});
@@ -172,12 +172,9 @@ MACRLCs = (
num_cc = 1;
tr_s_preference = "local_L1";
tr_n_preference = "f1";
local_n_if_name = "enp193s0f0";
local_n_address = "172.21.16.51";
remote_n_address = "172.21.6.90";
local_n_portc = 500;
local_n_portd = 2153;
remote_n_portc = 501;
remote_n_portd = 2153;
pusch_TargetSNRx10 = 170;
pucch_TargetSNRx10 = 230;
@@ -172,12 +172,9 @@ MACRLCs = (
num_cc = 1;
tr_s_preference = "local_L1";
tr_n_preference = "f1";
local_n_if_name = "enp193s0f0";
local_n_address = "172.21.16.51";
remote_n_address = "172.21.6.90";
local_n_portc = 500;
local_n_portd = 2153;
remote_n_portc = 501;
remote_n_portd = 2153;
pusch_TargetSNRx10 = 120;
pucch_TargetSNRx10 = 200;
@@ -172,12 +172,9 @@ MACRLCs = (
num_cc = 1;
tr_s_preference = "local_L1";
tr_n_preference = "f1";
local_n_if_name = "enp193s0f0";
local_n_address = "172.21.16.51";
remote_n_address = "172.21.6.90";
local_n_portc = 500;
local_n_portd = 2153;
remote_n_portc = 501;
remote_n_portd = 2153;
pusch_TargetSNRx10 = 120;
pucch_TargetSNRx10 = 200;
@@ -160,12 +160,9 @@ MACRLCs = ({
num_cc = 1;
tr_s_preference = "local_L1";
tr_n_preference = "f1";
local_n_if_name = "lo";
local_n_address = "127.0.0.4";
remote_n_address = "127.0.0.3";
local_n_portc = 500;
local_n_portd = 2152;
remote_n_portc = 501;
remote_n_portd = 2152;
pusch_FailureThres = 1000;
});
@@ -160,12 +160,9 @@ MACRLCs = ({
num_cc = 1;
tr_s_preference = "local_L1";
tr_n_preference = "f1";
local_n_if_name = "lo";
local_n_address = "127.0.0.5";
remote_n_address = "127.0.0.3";
local_n_portc = 500;
local_n_portd = 2152;
remote_n_portc = 501;
remote_n_portd = 2152;
pusch_FailureThres = 1000;
});
+1
View File
@@ -0,0 +1 @@
add_subdirectory(nr-cuup)
+4
View File
@@ -0,0 +1,4 @@
add_executable(nr-cuup-load-test nr-cuup-load-test.c)
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
+78
View File
@@ -0,0 +1,78 @@
This is a simple tester for the CU-UP. It configures the CU-UP via E1, and can
stream data via GTP in uplink and downlink directions.
[[_TOC_]]
# Overview
From a schematic point of view, the tester/CU-UP interaction looks like this:
```
+-------+
+------>| CU-UP |<------+
| +-------+ |
| | |
| F1-U | E1 | NG-U
| (data) | (control) | (data)
| | |
| +-------+ |
+------>|tester |<------+
+-------+
```
The tester is for performance testing of a CU-UP, and behaves like an
integrated DU/CU-CP/UPF from the CU-UP's point of view.
The tester and CU-UP have an E1 connection through which control information is
exchanged, e.g., configuration of UEs/bearers and associated information. In
other words, towards the CU-UP, the tester appears like a CU-CP.
Further, for each UE/bearer, CU-UP is configured with GTP IP information and
the corresponding TEID. The CU-UP will forward downlink data arriving on NG-U
on the F1-U interface, and uplink data arriving on F1-U on the NG-U interface.
In other words, on the F1-U interface, the tester appears like a DU to the
CU-UP, and on the NG-U interface, the tester appears like a UPF to the CU-UP.
A test scenario is fixed to these steps:
1. The tester sets up a number of UEs via the E1 interface (it is possible to
see the message exchange in Wireshark).
2. The tester streams data in downlink/uplink (from the CU-UP's point of view)
and measures how much data is lost.
3. The tester releases all the UEs via the E1 interface, and disconnects from
the CU-UP.
Note: The tester uses the same GTP module as the CU-UP. Thus, data might not
only be lost at the GTP interface of the CU-UP, but also at the tester.
# Usage
You can build the CU-UP and the tester like so:
cd ~/openairinterface5g
mkdir build && cd build && cmake .. -GNinja && ninja nr-cuup nr-cuup-load-test params_libconfig
./tests/nr-cuup/nr-cuup-load-test
./nr-cuup -O ../tests/nr-cuup/load-test.conf
This builds both tester and CU-UP in the directory `build/`, then starts the
load tester with default values
- 1 UE
- 10s of Test
- 60 Mbps of traffic in both downlik/uplink with a packet size of 1400 bytes
To see the available options, run
./tests/nr-cuup/nr-cuup-load-test -h
The configuration file [`load-test.conf`](./load-test.conf) matches the default
tester configuration (F1-U GTP traffic over non-standard port 2153, NG-U GTP
over 2152, tests on localhost). The configuration file includes this
non-standard port 2153 as the GTP module, as of now, cannot bind on the same
interface and port for both F1-U and NG-U due to internal limitations.
# Limitations
- The tester does not yet create/remove UEs during a traffic test.
- It might be possible to integrate with an external GTP traffic generator,
but we did not test this.
+58
View File
@@ -0,0 +1,58 @@
Active_gNBs = ( "gNB-OAI");
# Asn1_verbosity, choice in: none, info, annoying
Asn1_verbosity = "none";
gNBs =
(
{
////////// Identification parameters:
gNB_ID = 0xe00;
gNB_CU_UP_ID = 0xe00;
# cell_type = "CELL_MACRO_GNB";
gNB_name = "gNB-OAI";
// Tracking area code, 0x0000 and 0xfffe are reserved values
tracking_area_code = 1;
plmn_list = ({ mcc = 222; mnc = 01; mnc_length = 2; snssaiList = ({ sst = 1, sd = 0xffffff }) });
tr_s_preference = "f1";
local_s_address = "127.0.0.6";
remote_s_address = "127.0.0.1";
local_s_portc = 501;
local_s_portd = 2153;
remote_s_portc = 500;
remote_s_portd = 2153;
# ------- SCTP definitions
SCTP :
{
# Number of streams to use in input/output
SCTP_INSTREAMS = 2;
SCTP_OUTSTREAMS = 2;
};
E1_INTERFACE =
(
{
type = "up";
ipv4_cucp = "127.0.0.1";
ipv4_cuup = "127.0.0.6";
}
)
NETWORK_INTERFACES :
{
GNB_IPV4_ADDRESS_FOR_NG_AMF = "172.21.16.109";
GNB_IPV4_ADDRESS_FOR_NGU = "127.0.0.7";
GNB_PORT_FOR_S1U = 2152; # Spec 2152
};
}
);
log_config: {
global_log_level = "info";
};
+673
View File
@@ -0,0 +1,673 @@
/*
* Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The OpenAirInterface Software Alliance licenses this file to You under
* the OAI Public License, Version 1.1 (the "License"); you may not use this file
* except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.openairinterface.org/?page_id=698
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*-------------------------------------------------------------------------------
* For more information about the OpenAirInterface (OAI) Software Alliance:
* contact@openairinterface.org
*/
#include <stdio.h>
#include <arpa/inet.h>
#include <time.h>
#include "common/utils/LOG/log.h"
#include "common/utils/ocp_itti/intertask_interface.h" // this pulls in all of OAI+4G+5G
#include "openair3/SCTP/sctp_eNB_task.h"
#include "openair2/E1AP/e1ap.h"
#include "openair3/ocp-gtpu/gtp_itf.h"
configmodule_interface_t *uniqCfg;
#define DL 0
#define UL 1
int nr_rlc_get_available_tx_space(const rnti_t rntiP, const logical_chan_id_t channel_idP) { abort(); return 0; } /* in GTP */
softmodem_params_t *get_softmodem_params(void) { static softmodem_params_t p = {0}; return &p; }; /* in ITTI */
void e1_bearer_context_setup(const e1ap_bearer_setup_req_t *req) { abort(); } /* CU-UP */
void e1_bearer_context_modif(const e1ap_bearer_mod_req_t *req) { abort(); } /* CU-UP */
void e1_bearer_release_cmd(const e1ap_bearer_release_cmd_t *cmd) { abort(); } /* CU-UP */
void e1_reset(void) { abort(); } /* CU-UP */
instance_t *N3GTPUInst = NULL; /* CU-UP */
instance_t CUuniqInstance=0; /* CU-UP */
void exit_function(const char *file, const char *function, const int line, const char *s, const int assert)
{
LOG_E(GNB_APP, "error at %s:%d:%s: %s\n", file, line, function, s);
abort();
}
/* @brief Dummy function to pass a fptr to ITTI for staring RRC queue, without
* actually handling any messages. Functions called by main() will send/receive
* ITTI messages to E1, as if it was RRC. */
static void *cuup_tester_rrc(void *)
{
itti_mark_task_ready(TASK_RRC_GNB);
LOG_I(GNB_APP, "created RRC queue\n");
return NULL;
}
/* @brief Initialize E1 interface (as if it was CU-CP) */
static void init_e1()
{
net_ip_address_t ip = {.ipv4 = 1, .ipv4_address = "127.0.0.1" };
e1ap_net_config_t conf = { .CUCP_e1_ip_address = ip };
MessageDef *msg = itti_alloc_new_message(TASK_GNB_APP, 0, E1AP_REGISTER_REQ);
e1ap_net_config_t *e1ap_nc = &E1AP_REGISTER_REQ(msg).net_config;
*e1ap_nc = conf;
itti_send_msg_to_task(TASK_CUCP_E1, 0, msg);
}
/* @brief Setup CU-UP connection and retrieve parameters for further
* connection.
*
* Waits for E1 Setup Request, sends response, and returns SCTP association ID
* and NSSAI used by this CU-UP.
*/
static void setup_cuup(sctp_assoc_t *assoc_id, e1ap_nssai_t *nssai)
{
/* Wait for E1AP setup request, store CU-UP info */
MessageDef *itti_req = NULL;
itti_receive_msg(TASK_RRC_GNB, &itti_req);
MessagesIds id = ITTI_MSG_ID(itti_req);
AssertFatal(id == E1AP_SETUP_REQ, "expected E1AP setup request, received %s instead\n", itti_get_message_name(id));
*assoc_id = itti_req->ittiMsgHeader.originInstance;
e1ap_setup_req_t *req = &E1AP_SETUP_REQ(itti_req);
long t_id = req->transac_id;
DevAssert(req->supported_plmns = 1);
DevAssert(req->plmn[0].slice != NULL);
*nssai = *req->plmn[0].slice;
DevAssert(assoc_id != 0);
itti_free(TASK_GNB_APP, itti_req);
// acknowledge the E1 setup request with a response
MessageDef *itti_resp = itti_alloc_new_message(TASK_GNB_APP, 0, E1AP_SETUP_RESP);
itti_resp->ittiMsgHeader.originInstance = *assoc_id;
e1ap_setup_resp_t *resp = &E1AP_SETUP_RESP(itti_resp);
resp->transac_id = t_id;
itti_send_msg_to_task(TASK_CUCP_E1, 0, itti_resp);
}
/* @brief get E1AP bearer setup request based on parameters */
static e1ap_bearer_setup_req_t get_breq(uint32_t ue_id, long pdu_id, long drb_id, const e1ap_nssai_t *nssai, const UP_TL_information_t *tnl)
{
e1ap_bearer_setup_req_t bearer_req = {
.gNB_cu_cp_ue_id = ue_id,
.secInfo.cipheringAlgorithm = 0, /* no ciphering/NEA0 */
.secInfo.integrityProtectionAlgorithm = 0, /* no integrity/NIA0 */
/* encryptionKey not needed */
/* integrityProtectionKey not needed */
.numPDUSessions = 1,
};
pdu_session_to_setup_t *pdu = &bearer_req.pduSession[0];
pdu->sessionId = pdu_id;
pdu->nssai = *nssai;
pdu->securityIndication.integrityProtectionIndication = SECURITY_NOT_NEEDED;
pdu->securityIndication.confidentialityProtectionIndication = SECURITY_NOT_NEEDED;
pdu->UP_TL_information = *tnl;
pdu->numDRB2Setup = 1;
DRB_nGRAN_to_setup_t *drb = &pdu->DRBnGRanList[0];
drb->id = drb_id;
drb->numQosFlow2Setup = 1;
drb->numCellGroups = 1;
return bearer_req;
}
/* @brief check for packet loss by verifing the sequence number. Can handle
* packet losses and reordering.
* @param payload the sequence number of the current packet
* @param expected the expected sequence number
* @param [inout] pointer to number of lost packets, which will be updated by
* the difference between payload and expected.
* @return the next expected packet number. */
static uint32_t estimate_packetloss(const uint32_t payload, uint32_t expected, uint32_t *lost)
{
if (payload == expected) {
/* all good */
return expected + 1;
} else if (payload > expected) {
/* there was packet loss, assume all packets in between have been lost */
uint32_t diff = payload - expected;
*lost += diff;
LOG_W(GNB_APP, "packet loss: expected %d received %d diff %d (total loss %d)\n", expected, payload, diff, *lost);
return payload + 1;
} else { /* payload < expected */
/* reordered: we assumed current packet to be lost, so reduce the
* loss for this packet as it arrived in the end. */
*lost -= 1;
LOG_W(GNB_APP, "packet reordering occurred: expected %d received %d\n", expected, payload);
return expected; /* we still wait for the next packet */
}
}
static struct ue_stat {
uint32_t count;
uint32_t lost;
uint64_t received;
} ue_stat[10][2] = {0};
/* @brief ulReceived callback for F1-U
*
* Checks for packet loss by verifying that the first 4 bytes are corresponding
* to a continuous 4-byte (word) enumeration. */
static bool recv_ng(protocol_ctxt_t *ctxt,
const ue_id_t ue_id,
const srb_flag_t flag,
const rb_id_t rb,
const mui_t mui,
const confirm_t confirm,
const sdu_size_t size,
unsigned char *const buf,
const pdcp_transmission_mode_t modeP,
const uint32_t *sourceL2Id,
const uint32_t *destinationL2Id,
const uint8_t qfi,
const bool rqi,
const int pdusession_id)
{
uint32_t *payload = (uint32_t *) buf;
DevAssert(size % 4 == 0);
struct ue_stat *s = &ue_stat[ue_id][UL];
s->count = estimate_packetloss(*payload, s->count, &s->lost);
s->received += size;
return true;
}
/* @brief Set up the NG-U (north-bound) GTP tunnel, and inform CU-UP via E1 */
static up_params_t setup_cuup_ue_ng(sctp_assoc_t assoc_id, const e1ap_nssai_t *nssai, uint32_t ue_id, instance_t gtp_inst, const char *ip)
{
long pdu_id = 1;
long drb_id = pdu_id + 3;
in_addr_t addr_lo;
inet_pton(AF_INET, ip, &addr_lo);
/* create the local tunnel (i.e., as if we were UPF) */
transport_layer_addr_t null_addr = {.length = 32};
teid_t teid_lo = newGtpuCreateTunnel(gtp_inst, ue_id, pdu_id, pdu_id, -1, -1, null_addr, NULL, recv_ng);
UP_TL_information_t tnl = {.teId = teid_lo};
memcpy(&tnl.tlAddress, &addr_lo, 4);
/* Create a new bearer */
MessageDef *itti_breq = itti_alloc_new_message(TASK_GNB_APP, 0, E1AP_BEARER_CONTEXT_SETUP_REQ);
itti_breq->ittiMsgHeader.originInstance = assoc_id;
e1ap_bearer_setup_req_t *breq = &E1AP_BEARER_CONTEXT_SETUP_REQ(itti_breq);
*breq = get_breq(ue_id, pdu_id, drb_id, nssai, &tnl);
itti_send_msg_to_task(TASK_CUCP_E1, 0, itti_breq);
/* Wait for the ack */
MessageDef *itti_bresp;
itti_receive_msg(TASK_RRC_GNB, &itti_bresp);
MessagesIds id = ITTI_MSG_ID(itti_bresp);
AssertFatal(id == E1AP_BEARER_CONTEXT_SETUP_RESP, "expected E1AP bearer context setup response, received %s instead\n", itti_get_message_name(id));
DevAssert(assoc_id == itti_bresp->ittiMsgHeader.originInstance);
e1ap_bearer_setup_resp_t *bresp = &E1AP_BEARER_CONTEXT_SETUP_RESP(itti_bresp);
DevAssert(bresp->gNB_cu_cp_ue_id == ue_id);
DevAssert(bresp->gNB_cu_up_ue_id == ue_id);
DevAssert(bresp->numPDUSessions == 1);
teid_t teid_rm = bresp->pduSession[0].tl_info.teId;
in_addr_t addr_rm;
memcpy(&addr_rm, &bresp->pduSession[0].tl_info.tlAddress, 4);
DevAssert(bresp->pduSession[0].numDRBSetup == 1);
DRB_nGRAN_setup_t *drb = &bresp->pduSession[0].DRBnGRanList[0];
DevAssert(drb->id == drb_id);
DevAssert(drb->numUpParam == 1);
up_params_t f1_up = drb->UpParamList[0];
itti_free(TASK_GNB_APP, itti_bresp);
/* print diagnostics */
char ip_lo[32] = {0};
inet_ntop(AF_INET, &addr_lo, ip_lo, sizeof(ip_lo));
char ip_rm[32] = {0};
inet_ntop(AF_INET, &addr_rm, ip_rm, sizeof(ip_rm));
GtpuUpdateTunnelOutgoingAddressAndTeid(gtp_inst, ue_id, pdu_id, addr_rm, teid_rm);
LOG_I(GNB_APP, "CU-UP created NG-U, local %s/%x remote %s/%x\n", ip_lo, teid_lo, ip_rm, teid_rm);
inet_ntop(AF_INET, &f1_up.tl_info.tlAddress, ip_rm, sizeof(ip_rm));
LOG_I(GNB_APP, "CU-UP created F1-U, remove %s/%x\n", ip_rm, f1_up.tl_info.teId);
return f1_up;
}
/* @brief get E1AP bearer modification request based on parameters */
static e1ap_bearer_mod_req_t get_bmod(uint32_t ue_id, long pdu_id, up_params_t up)
{
e1ap_bearer_mod_req_t bearer_mod = {
.gNB_cu_cp_ue_id = ue_id,
.gNB_cu_up_ue_id = ue_id,
.numPDUSessionsMod = 1,
};
pdu_session_to_mod_t *pdum = &bearer_mod.pduSessionMod[0];
pdum->sessionId = pdu_id;
pdum->numDRB2Modify = 1;
DRB_nGRAN_to_mod_t *drb = &pdum->DRBnGRanModList[0];
drb->id = 4;
drb->numDlUpParam = 1;
drb->DlUpParamList[0] = up;
return bearer_mod;
}
/* @brief Received callback for F1-U
*
* Checks for packet loss by verifying that the first 4 bytes are corresponding
* to a continuous 4-byte (word) enumeration. */
static bool recv_f1(protocol_ctxt_t *ctxt,
const srb_flag_t flag,
const rb_id_t rb,
const mui_t mui,
const confirm_t confirm,
const sdu_size_t size,
unsigned char *const buf,
const pdcp_transmission_mode_t modeP,
const uint32_t *sourceL2Id,
const uint32_t *destinationL2Id)
{
int skip_bytes = 2;
uint32_t *payload = (uint32_t *) &buf[skip_bytes];
DevAssert((size - skip_bytes) % 4 == 0);
int ue_id = ctxt->rntiMaybeUEid;
struct ue_stat *s = &ue_stat[ue_id][DL];
s->count = estimate_packetloss(*payload, s->count, &s->lost);
s->received += size - skip_bytes;
return true;
}
/* @brief Set up the F1-U (south-bound) GTP tunnel, and inform CU-UP via E1 */
static void setup_cuup_ue_f1(sctp_assoc_t assoc_id, uint32_t ue_id, instance_t gtp_inst, const char *ip, up_params_t rm)
{
int pdu_id = 1;
int drb_id = pdu_id + 3;
/* create tunnel (i.e., as if we were DU) */
transport_layer_addr_t addr = {.length = 32};
memcpy(addr.buffer, &rm.tl_info.tlAddress, 4);
teid_t teid_lo = newGtpuCreateTunnel(gtp_inst, ue_id, drb_id, drb_id, rm.tl_info.teId, -1, addr, recv_f1, NULL);
up_params_t lo = {.tl_info.teId = teid_lo, .cell_group_id = rm.cell_group_id};
inet_pton(AF_INET, ip, &lo.tl_info.tlAddress);
/* modify existing tunnel via E1 to pass in F1-U TEID */
MessageDef *itti_bmod = itti_alloc_new_message(TASK_GNB_APP, 0, E1AP_BEARER_CONTEXT_MODIFICATION_REQ);
itti_bmod->ittiMsgHeader.originInstance = assoc_id;
e1ap_bearer_mod_req_t *bmod = &E1AP_BEARER_CONTEXT_MODIFICATION_REQ(itti_bmod);
*bmod = get_bmod(ue_id, pdu_id, lo);
itti_send_msg_to_task(TASK_CUCP_E1, 0, itti_bmod);
/* Wait for the ack */
MessageDef *itti_bmodr;
itti_receive_msg(TASK_RRC_GNB, &itti_bmodr);
MessagesIds id = ITTI_MSG_ID(itti_bmodr);
AssertFatal(id == E1AP_BEARER_CONTEXT_MODIFICATION_RESP, "expected E1AP bearer context modification response, received %s instead\n", itti_get_message_name(id));
//DevAssert(assoc_id == itti_bmodr->ittiMsgHeader.originInstance);
itti_free(TASK_GNB_APP, itti_bmodr);
/* print diagnostics */
char ip_lo[32] = {0};
inet_ntop(AF_INET, &lo.tl_info.tlAddress, ip_lo, sizeof(ip_lo));
char ip_rm[32] = {0};
inet_ntop(AF_INET, &rm.tl_info.tlAddress, ip_rm, sizeof(ip_rm));
LOG_I(GNB_APP, "CU-UP created F1-U, local %s/%x remote %s/%x\n", ip_lo, lo.tl_info.teId, ip_rm, rm.tl_info.teId);
}
/* @brief release the UE over E1, and delete corresponding GTP tunnels */
static void e1_release_ue(sctp_assoc_t assoc_id, uint32_t ue_id, instance_t f1_inst, instance_t ng_inst)
{
MessageDef *itti_cmd = itti_alloc_new_message(TASK_GNB_APP, 0, E1AP_BEARER_CONTEXT_RELEASE_CMD);
itti_cmd->ittiMsgHeader.originInstance = assoc_id;
e1ap_bearer_release_cmd_t *cmd = &E1AP_BEARER_CONTEXT_RELEASE_CMD(itti_cmd);
cmd->gNB_cu_cp_ue_id = cmd->gNB_cu_up_ue_id = ue_id;
cmd->cause.type = E1AP_CAUSE_RADIO_NETWORK;
cmd->cause.value = E1AP_RADIO_CAUSE_NORMAL_RELEASE;
itti_send_msg_to_task(TASK_CUCP_E1, 0, itti_cmd);
/* Wait for the ack */
MessageDef *itti_cplt;
itti_receive_msg(TASK_RRC_GNB, &itti_cplt);
MessagesIds id = ITTI_MSG_ID(itti_cplt);
AssertFatal(id == E1AP_BEARER_CONTEXT_RELEASE_CPLT, "expected E1AP bearer context release complete, received %s instead\n", itti_get_message_name(id));
itti_free(TASK_GNB_APP, itti_cplt);
newGtpuDeleteAllTunnels(f1_inst, ue_id);
newGtpuDeleteAllTunnels(ng_inst, ue_id);
}
/* @brief initialize GTP interface for IP:port */
static instance_t init_gtp(const char *ip, uint16_t port)
{
openAddr_t iface = {0};
DevAssert(strlen(ip) <= sizeof(iface.originHost));
strcpy(iface.originHost, ip);
snprintf(iface.originService, sizeof(iface.originService), "%d", port);
snprintf(iface.destinationService, sizeof(iface.destinationService), "%d", port);
return gtpv1Init(iface);
}
/* @brief calculate time difference of two struct timespec */
static float time_diff(struct timespec s, struct timespec e)
{
//printf("start %ld.%ld end %ld.%ld\n", s.tv_sec, s.tv_nsec, e.tv_sec, e.tv_nsec);
float dur = e.tv_sec - s.tv_sec + (e.tv_nsec - s.tv_nsec) / 1000000000.f;
//printf("duration %f s %f us\n", dur, dur * 1000000.f);
return dur * 1000000.f;
}
enum pdcp_len {PDCP_SN_LEN_12, PDCP_SN_LEN_18};
/* @brief generate hardcoded PDCP header in case of UL transmission */
static void write_pdcp_header(uint32_t sn, enum pdcp_len len, uint8_t *buf)
{
DevAssert(len == PDCP_SN_LEN_12);
/* see nr_pdcp_entity_process_sdu() or 38.323 sec 6.2.2.2 */
buf[0] = 0x80 | ((sn >> 8) & 0xf);
buf[1] = sn & 0xff;
//printf("%02x %02x\n", buf[0], buf[1]);
}
/* @brief the configuration for a thread to send GTP traffic */
struct thr_data {
/// pthread ID to join this thread
pthread_t t;
/* in */
/// GTP instance to send on
instance_t inst;
/// UE ID to be used
ue_id_t ue_id;
/// flag to decide if PDCP/SDAP header is to be prepended
bool is_ul;
/// length of data packet
unsigned int data_len;
/// target throughput to reach
uint64_t throughput;
/// duration of the data traffic to last
unsigned int duration;
/* out */
/// exact measured transmission time
float total_time;
/// exact measured data that has been sent
uint64_t total_data;
};
/* @brief Main traffic generation/transmission function.
*
* The function calculates the amount of packets to be sent during each TTI
* (one millisecond) (as a float, to send the exact amount of packets). It then
* enters a loop, in which (1) it sends the packets of the current TTI, possibly
* including additional packets if they could not be sent in the last TTI, (2)
* measure the time since the last TTI boundary, and sleep, if necessary, to
* adjust to the exact throughput (3) update for additional packets to send if
* the last TTI took too long. The last point ensure that we send exactly an
* exact amount of data, possibly taking longer than specified.
*
* @param v pointer to generation function of type `struct thr_data`.
* @return always NULL */
static void *sender_thread(void *v)
{
struct thr_data *d = v;
size_t oh = d->is_ul ? 3 : 0;
size_t packet_len = d->data_len + oh;
uint8_t buf[packet_len];
memset(buf, 0, packet_len);
uint32_t *payload = (uint32_t *)(buf + oh);
uint64_t to_send = (float)d->throughput / 8.0 * d->duration;
const float pps_1ms = (float) d->throughput / 8.0 / d->data_len / 1000;
float pps_1ms_nextwin = pps_1ms;
int bearer = d->is_ul ? 4 : 1;
//printf("sending %ld Mbps for %ld s => data %ld, %.3f pps\n", (float) d->throughput / 1000000.0, d->duration, to_send, pps_1ms);
int i_1ms = 0;
struct timespec start;
clock_gettime(CLOCK_MONOTONIC, &start);
size_t packet_count = 0;
while (true) {
int pps_now = pps_1ms_nextwin;
pps_1ms_nextwin = pps_1ms + pps_1ms_nextwin - (float) pps_now;
//printf("this window send %d packets next window %.3f (pps_1ms %.3f)\n", pps_now, pps_1ms_nextwin, pps_1ms);
for (uint32_t i = 0; i < pps_now; ++i) {
if (d->is_ul)
write_pdcp_header(packet_count, PDCP_SN_LEN_12, buf);
*payload = packet_count++;
gtpv1uSendDirect(d->inst, d->ue_id, bearer, (uint8_t *)buf, packet_len, false, false);
d->total_data += d->data_len;
}
struct timespec t;
clock_gettime(CLOCK_MONOTONIC, &t);
float diff_us = time_diff(start, t);
int us_passed = diff_us - i_1ms*1000;
int iter_advance = us_passed / 1000 + 1;
int wait_usec = 1000 - us_passed;
//printf("diff %.3fus i_1ms %d us_passed %d iter %d wait %d (%.3f %%)\n", diff_us, i_1ms, us_passed, iter_advance, wait_usec, 100.0 * d->total_data / to_send);
DevAssert(iter_advance > 0);
if (wait_usec > 0)
usleep(wait_usec);
if (iter_advance > 1) /* we have to send more in the next window */
pps_1ms_nextwin += (iter_advance - 1) * pps_1ms_nextwin;
i_1ms += iter_advance;
if (d->total_data >= to_send)
break;
}
struct timespec end;
clock_gettime(CLOCK_MONOTONIC, &end);
d->total_time = time_diff(start, end) / 1000000.f;
return NULL;
}
/* @brief print statistics of a UE (amount of data, achieved rate, if data was
* lost). */
static bool print_stats(const char *pref, int ue_id, uint64_t total_data, float duration, const struct ue_stat *s)
{
float tthr = 8.0 * total_data / duration / 1000000.f;
float rthr = 8.0 * s->received / duration / 1000000.f;
LOG_I(GNB_APP, "%d %s sent %ld in %.3f s received %ld bytes => rates [Mbps] transmit %.3f receive %.3f\n",
ue_id,
pref,
total_data,
duration,
s->received,
tthr,
rthr);
if (total_data == s->received) {
DevAssert(s->lost == 0);
LOG_A(GNB_APP, "%d %s received all data sent\n", ue_id, pref);
return true;
} else {
uint64_t m = total_data - s->received;
LOG_W(GNB_APP, "%d %s warning: missing %ld bytes (%.3f %%, %d packets)\n", ue_id, pref, m, 100.f * m / total_data, s->lost);
return false;
}
}
/* @brief start a thread if specified through template, and in direction of
* given GTP bearer.
* @param template specifies if thread is to be started (throughput and
* duration are positive). If so, the template data is stored inside the
* thread.
* @param thr place to store thread data
* @param b the GTP bearer to use */
static struct thr_data *start_thread(const struct thr_data *template, struct thr_data *thr, instance_t inst, ue_id_t ue_id)
{
if (template->throughput == 0 || template->duration == 0)
return NULL;
*thr = *template;
thr->inst = inst;
thr->ue_id = ue_id;
pthread_create(&thr->t, NULL, sender_thread, thr);
return thr;
}
/* @brief print progress of a thread specified in thr_data, and prepend DL/UL
* based on dir. data access to thread data is not private, but we only read,
* so don't care. */
static bool print_progress(const int dir, const struct thr_data *obs)
{
if (!obs)
return false;
uint64_t target = (uint64_t) obs->throughput / 8 * obs->duration;
float progress = (float)obs->total_data / target;
printf(" %s sent %3.f %%", dir == DL ? "DL" : "UL", 100.0 * progress);
return progress < 0.99;
}
int main(int argc, char *argv[])
{
if ((uniqCfg = load_configmodule(argc, argv, CONFIG_ENABLECMDLINEONLY)) == NULL) {
exit_fun("[SOFTMODEM] Error, configuration module init failed\n");
}
logInit();
itti_init(TASK_MAX, tasks_info);
unsigned int n_ue;
unsigned int duration_s;
unsigned int packet_size;
uint64_t target_rate_dl;
uint64_t target_rate_ul;
char *ng_ip = malloc_or_fail(100);
uint16_t ng_port;
char *f1_ip = malloc_or_fail(100);
uint16_t f1_port;
paramdef_t param_def[] = {
{"n", "number of UEs\n", 0, .uptr = &n_ue, .defuintval = 1, TYPE_UINT},
{"t", "duration of test (s)\n", 0, .uptr = &duration_s, .defuintval = 10, TYPE_UINT},
{"p", "packet size\n", 0, .uptr = &packet_size, .defuintval = 1400, TYPE_UINT},
{"d", "DL target rate (Mbps, per UE)\n", 0, .u64ptr = &target_rate_dl, .defint64val = 60, TYPE_UINT64},
{"u", "UL target rate (Mbps, per UE)\n", 0, .u64ptr = &target_rate_ul, .defint64val = 60, TYPE_UINT64},
{"ng-ip", "Tester NG-U IP\n", 0, .strptr = &ng_ip, .defstrval = "127.0.0.1", TYPE_STRING, 100},
{"ng-port", "Tester NG-U port\n", 0, .u16ptr = &ng_port, .defuintval = 2152, TYPE_UINT16},
{"f1-ip", "Tester F1-U IP\n", 0, .strptr = &f1_ip, .defstrval = "127.0.0.1", TYPE_STRING, 100},
{"f1-port", "Tester F1-U port\n", 0, .u16ptr = &f1_port, .defuintval = 2153, TYPE_UINT16},
};
config_get(uniqCfg, param_def, sizeofArray(param_def), NULL);
// on command line, target rate is in Mbps, convert to bps
target_rate_dl *= 1000000LL;
target_rate_ul *= 1000000LL;
LOG_I(GNB_APP,
"CU-UP load tester: running test for\n"
"- Number of UEs %d\n"
"- Duration %d seconds\n"
"- Packet size %d\n"
"- Target DL rate %.3f Mbps (per UE)\n"
"- Target UL rate %.3f Mbps (per UE)\n"
"- Tester endpoint for CU-UP NG-U: %s:%d\n"
"- Tester endpoint for CU-UP F1-U: %s:%d\n",
n_ue, duration_s, packet_size,
(float) target_rate_dl / 1e6,
(float) target_rate_ul / 1e6,
ng_ip, ng_port, f1_ip, f1_port);
struct thr_data dl_template = {.is_ul = false, .data_len = packet_size, .throughput = target_rate_dl, .duration = duration_s};
struct thr_data ul_template = {.is_ul = true, .data_len = packet_size, .throughput = target_rate_ul, .duration = duration_s};
/* init GTP for NG-U comm with CU-UP */
instance_t ng_inst = init_gtp(ng_ip, ng_port);
AssertFatal(ng_inst > 0, "could not initialize GTP interface on %s:%d towards CU-UP NG-U\n", ng_ip, ng_port);
/* init GTP for F1-U comm with CU-UP */
instance_t f1_inst = init_gtp(f1_ip, f1_port);
AssertFatal(f1_inst > 0, "could not initialize GTP interface on %s:%d towards CU-UP F1-U\n", f1_ip, f1_port);
/* Start relevant services: SCTP, CUCP_E1: E1; GTP not actively used; RRC to
* register the queue so we can receive what E1 sends us back */
int rc;
rc = itti_create_task(TASK_SCTP, sctp_eNB_task, NULL);
AssertFatal(rc >= 0, "Create task for SCTP failed\n");
rc = itti_create_task(TASK_CUCP_E1, E1AP_CUCP_task, NULL);
AssertFatal(rc >= 0, "Create task for CUUP E1 failed\n");
rc = itti_create_task(TASK_GTPV1_U, gtpv1uTask, NULL);
AssertFatal(rc >= 0, "Create task for GTPV1U failed\n");
rc = itti_create_task(TASK_RRC_GNB, cuup_tester_rrc, NULL);
AssertFatal(rc >= 0, "Create task for RRC failed\n");
init_e1();
/* Set up CU-UP connection: waits for CU-UP-initiated SCTP+E1 setup request */
sctp_assoc_t assoc_id;
e1ap_nssai_t nssai;
setup_cuup(&assoc_id, &nssai);
/* Set up UE contexts in CU-UP. the below two calls correspond to
* - setup_cuup_ue_ng(): E1 bearer context setup <=> completely sets up NG-U
* in CU-UP, gets TEID for F1-U UL
* - setup_cuup_ue_f1(): E1 bearer context modification <=> send TEID for
* F1-U DL (as if we had talked to the DU in the middle) */
for (int ue = 0; ue < n_ue; ++ue) {
up_params_t f1_up = setup_cuup_ue_ng(assoc_id, &nssai, ue, ng_inst, ng_ip);
setup_cuup_ue_f1(assoc_id, ue, f1_inst, f1_ip, f1_up);
}
usleep(1000);
/* for each UE, start DL&UL data streams in dedicated thread, if configured */
struct thr_data thrd[n_ue][2];
memset(thrd, 0, sizeof(thrd));
struct thr_data *obs_dl = NULL, *obs_ul = NULL;
for (int ue = 0; ue < n_ue; ++ue) {
obs_dl = start_thread(&dl_template, &thrd[ue][DL], ng_inst, ue);
obs_ul = start_thread(&ul_template, &thrd[ue][UL], f1_inst, ue);
}
/* show progress of one thread (all take the same time) */
while (obs_dl || obs_ul) {
bool dlrunning = print_progress(DL, obs_dl);
bool ulrunning = print_progress(UL, obs_ul);
printf("\r");
fflush(stdout);
if (!dlrunning && !ulrunning)
break;
usleep(100000);
}
/* join threads, if applicable */
for (int ue = 0; ue < n_ue; ++ue) {
struct thr_data *dl = &thrd[ue][DL];
if (dl->t)
pthread_join(dl->t, NULL);
struct thr_data *ul = &thrd[ue][UL];
if (ul->t)
pthread_join(ul->t, NULL);
}
/* wait a bit, to ensure all data is received after having sent it above */
sleep(1);
for (int ue = 0; ue < n_ue; ++ue) {
e1_release_ue(assoc_id, ue, f1_inst, ng_inst);
}
/* show stats of all threads */
bool success = true;
for (int ue = 0; ue < n_ue; ++ue) {
struct thr_data *thrddl = &thrd[ue][DL];
struct ue_stat *statdl = &ue_stat[ue][DL];
if (thrddl->t)
if (!print_stats("DL", ue, thrddl->total_data, thrddl->total_time, statdl))
success = false;
struct thr_data *thrdul = &thrd[ue][UL];
struct ue_stat *statul = &ue_stat[ue][UL];
if (thrdul->t)
if (!print_stats("UL", ue, thrdul->total_data, thrdul->total_time, statul))
success = false;
}
if (success)
LOG_A(GNB_APP, "test succeeded\n");
else
LOG_W(GNB_APP, "TEST FAILED\n");
return success ? 0 : 1;
}