From 40cfe2e5f24d9bb7fc57a52858dd5e322d707e95 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 12 Feb 2026 06:02:59 +0100 Subject: [PATCH 1/8] Remove unused GTP types --- openair3/ocp-gtpu/gtp_itf.h | 42 ------------------------------------- 1 file changed, 42 deletions(-) diff --git a/openair3/ocp-gtpu/gtp_itf.h b/openair3/ocp-gtpu/gtp_itf.h index 9274cd52f2..8139eb3667 100644 --- a/openair3/ocp-gtpu/gtp_itf.h +++ b/openair3/ocp-gtpu/gtp_itf.h @@ -132,48 +132,6 @@ typedef struct gtpv1u_gnb_delete_tunnel_req_s gtpv1u_gnb_delete_tunnel_req_t; instance_t gtpv1Init(openAddr_t context); void *gtpv1uTask(void *args); -#define GTPV1U_BEARER_OFFSET 3 -#define GTPV1U_MAX_BEARERS_ID (32 - GTPV1U_BEARER_OFFSET) /* max_val_LTE_DRB_Identity */ - typedef enum { - BEARER_DOWN = 0, - BEARER_IN_CONFIG, - BEARER_UP, - BEARER_DL_HANDOVER, - BEARER_UL_HANDOVER, - BEARER_MAX, - } bearer_state_t; - - typedef struct fixMe_gtpv1u_bearer_s { - /* TEID used in dl and ul */ - teid_t teid_eNB; ///< eNB TEID - uintptr_t teid_eNB_stack_session; ///< eNB TEID - teid_t teid_sgw; ///< Remote TEID - in_addr_t sgw_ip_addr; - struct in6_addr sgw_ip6_addr; - teid_t teid_teNB; - in_addr_t tenb_ip_addr; ///< target eNB ipv4 - struct in6_addr tenb_ip6_addr; ///< target eNB ipv6 - tcp_udp_port_t port; - //NwGtpv1uStackSessionHandleT stack_session; - bearer_state_t state; - } fixMe_gtpv1u_bearer_t; - - typedef struct gtpv1u_ue_data_s { - /* UE identifier for oaisim stack */ - rnti_t ue_id; - - /* Unique identifier used between PDCP and GTP-U to distinguish UEs */ - uint32_t instance_id; - int num_bearers; - /* Bearer related data. - * Note that the first LCID available for data is 3 and we fixed the maximum - * number of e-rab per UE to be (32 [id range]), max RB is 11. The real rb id will 3 + rab_id (3..32). - */ - fixMe_gtpv1u_bearer_t bearers[GTPV1U_MAX_BEARERS_ID]; - - //RB_ENTRY(gtpv1u_ue_data_s) gtpv1u_ue_node; - } gtpv1u_ue_data_t; - #ifdef __cplusplus } #endif From d9d18f07fde2a91759e2dcd5f31e970facd6b9d1 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 12 Feb 2026 06:06:19 +0100 Subject: [PATCH 2/8] Remove unused functions --- openair2/RRC/LTE/rrc_eNB_GTPV1U.c | 162 ------------------------------ openair2/RRC/LTE/rrc_eNB_GTPV1U.h | 9 -- 2 files changed, 171 deletions(-) diff --git a/openair2/RRC/LTE/rrc_eNB_GTPV1U.c b/openair2/RRC/LTE/rrc_eNB_GTPV1U.c index 7010c7c831..4088e2b0c6 100644 --- a/openair2/RRC/LTE/rrc_eNB_GTPV1U.c +++ b/openair2/RRC/LTE/rrc_eNB_GTPV1U.c @@ -82,168 +82,6 @@ rrc_eNB_process_GTPV1U_CREATE_TUNNEL_RESP( } } -//------------------------------------------------------------------------------ -bool gtpv_data_req(const protocol_ctxt_t* const ctxt_pP, - const rb_id_t rb_idP, - const mui_t muiP, - const confirm_t confirmP, - const sdu_size_t sdu_sizeP, - uint8_t* const buffer_pP, - const pdcp_transmission_mode_t modeP, - uint32_t task_id) -//------------------------------------------------------------------------------ -{ - if(sdu_sizeP == 0) - { - LOG_I(GTPU,"gtpv_data_req sdu_sizeP == 0"); - return false; - } - LOG_D(GTPU, "gtpv_data_req ue rnti %lx sdu_sizeP %d rb id %ld", ctxt_pP->rntiMaybeUEid, sdu_sizeP, rb_idP); - MessageDef *message_p; - // Uses a new buffer to avoid issue with PDCP buffer content that could be changed by PDCP (asynchronous message handling). - uint8_t *message_buffer; - - if(task_id == TASK_DATA_FORWARDING){ - - LOG_I(GTPU,"gtpv_data_req task_id = TASK_DATA_FORWARDING\n"); - - message_buffer = itti_malloc (TASK_GTPV1_U, TASK_DATA_FORWARDING, sdu_sizeP); - - memcpy (message_buffer, buffer_pP, sdu_sizeP); - - message_p = itti_alloc_new_message (TASK_GTPV1_U, 0, GTPV1U_ENB_DATA_FORWARDING_IND); - GTPV1U_ENB_DATA_FORWARDING_IND (message_p).frame = ctxt_pP->frame; - GTPV1U_ENB_DATA_FORWARDING_IND (message_p).enb_flag = ctxt_pP->enb_flag; - GTPV1U_ENB_DATA_FORWARDING_IND (message_p).rb_id = rb_idP; - GTPV1U_ENB_DATA_FORWARDING_IND (message_p).muip = muiP; - GTPV1U_ENB_DATA_FORWARDING_IND (message_p).confirmp = confirmP; - GTPV1U_ENB_DATA_FORWARDING_IND (message_p).sdu_size = sdu_sizeP; - GTPV1U_ENB_DATA_FORWARDING_IND (message_p).sdu_p = message_buffer; - GTPV1U_ENB_DATA_FORWARDING_IND (message_p).mode = modeP; - GTPV1U_ENB_DATA_FORWARDING_IND (message_p).module_id = ctxt_pP->module_id; - GTPV1U_ENB_DATA_FORWARDING_IND(message_p).rnti = ctxt_pP->rntiMaybeUEid; - GTPV1U_ENB_DATA_FORWARDING_IND (message_p).eNB_index = ctxt_pP->eNB_index; - - itti_send_msg_to_task (TASK_DATA_FORWARDING, ctxt_pP->instance, message_p); - return true; // TODO should be changed to a CNF message later, currently RRC lite does not used the returned value anyway. - } else if (task_id == TASK_END_MARKER){ - - LOG_I(GTPU,"gtpv_data_req task_id = TASK_END_MARKER\n"); - - message_buffer = itti_malloc (TASK_GTPV1_U, TASK_END_MARKER, sdu_sizeP); - - memcpy (message_buffer, buffer_pP, sdu_sizeP); - - message_p = itti_alloc_new_message (TASK_GTPV1_U, 0, GTPV1U_ENB_END_MARKER_IND); - GTPV1U_ENB_END_MARKER_IND (message_p).frame = ctxt_pP->frame; - GTPV1U_ENB_END_MARKER_IND (message_p).enb_flag = ctxt_pP->enb_flag; - GTPV1U_ENB_END_MARKER_IND (message_p).rb_id = rb_idP; - GTPV1U_ENB_END_MARKER_IND (message_p).muip = muiP; - GTPV1U_ENB_END_MARKER_IND (message_p).confirmp = confirmP; - GTPV1U_ENB_END_MARKER_IND (message_p).sdu_size = sdu_sizeP; - GTPV1U_ENB_END_MARKER_IND (message_p).sdu_p = message_buffer; - GTPV1U_ENB_END_MARKER_IND (message_p).mode = modeP; - GTPV1U_ENB_END_MARKER_IND (message_p).module_id = ctxt_pP->module_id; - GTPV1U_ENB_END_MARKER_IND(message_p).rnti = ctxt_pP->rntiMaybeUEid; - GTPV1U_ENB_END_MARKER_IND (message_p).eNB_index = ctxt_pP->eNB_index; - - itti_send_msg_to_task (TASK_END_MARKER, ctxt_pP->instance, message_p); - return true; // TODO should be changed to a CNF message later, currently RRC lite does not used the returned value anyway. - } - LOG_E(RRC, "Impossible state\n"); - return false; -} - -bool gtpv_data_req_new(protocol_ctxt_t *ctxt, - const srb_flag_t srb_flagP, - const rb_id_t rb_idP, - const mui_t muiP, - const confirm_t confirmP, - const sdu_size_t sdu_buffer_sizeP, - unsigned char *const sdu_buffer_pP, - const pdcp_transmission_mode_t modeP, - const uint32_t *sourceL2Id, - const uint32_t *destinationL2Id) { - int task; - - if (sdu_buffer_sizeP==0) - task=TASK_END_MARKER; - else - task=TASK_DATA_FORWARDING; - - struct rrc_eNB_ue_context_s *ue_context_p = rrc_eNB_get_ue_context(RC.rrc[ctxt->module_id], ctxt->rntiMaybeUEid); - if(ue_context_p == NULL || ue_context_p->ue_context.handover_info == NULL || - ue_context_p->ue_context.StatusRrc != RRC_HO_EXECUTION) { - LOG_E(RRC,"incoming GTP-U for X2 in non HO context\n"); - return false; - } - - /* in the source enb, UE in RRC_HO_EXECUTION mode */ - // We have 2*2=4 cases (data or end marker) * (from slave, from EPC) - if (ue_context_p->ue_context.handover_info->state == HO_COMPLETE) { - LOG_I(GTPU, "source receive END MARKER\n"); - /* set handover state */ - //ue_context_p->ue_context.handover_info->state = HO_END_MARKER; - MessageDef *msg; - // Configure end marker - msg = itti_alloc_new_message(TASK_GTPV1_U, 0, GTPV1U_ENB_END_MARKER_REQ); - GTPV1U_ENB_END_MARKER_REQ(msg).buffer = itti_malloc(TASK_GTPV1_U, TASK_GTPV1_U, GTPU_HEADER_OVERHEAD_MAX + sdu_buffer_sizeP); - memcpy(>PV1U_ENB_END_MARKER_REQ(msg).buffer[GTPU_HEADER_OVERHEAD_MAX], sdu_buffer_pP, sdu_buffer_sizeP); - GTPV1U_ENB_END_MARKER_REQ(msg).length = sdu_buffer_sizeP; - GTPV1U_ENB_END_MARKER_REQ(msg).rnti = ctxt->rntiMaybeUEid; - GTPV1U_ENB_END_MARKER_REQ(msg).rab_id = rb_idP; - GTPV1U_ENB_END_MARKER_REQ(msg).offset = GTPU_HEADER_OVERHEAD_MAX; - LOG_I(GTPU, "Send End Marker to GTPV1-U at frame %d and subframe %d \n", ctxt->frame,ctxt->subframe); - itti_send_msg_to_task(TASK_GTPV1_U, ENB_MODULE_ID_TO_INSTANCE(ctxt->module_id), msg); - return 0; - } - - /* target enb */ - // We have 2*2=4 cases (data or end marker) * (from source, from EPC) - if (ue_context_p->ue_context.StatusRrc == RRC_RECONFIGURED) { - // It should come from remote eNB - // case end marker by EPC is not possible ? - if (task==TASK_END_MARKER) { - LOG_I(GTPU, "target end receive END MARKER\n"); - // We close the HO, nothing to send to remote ? - ue_context_p->ue_context.handover_info->state = HO_END_MARKER; - gtpv1u_enb_delete_tunnel_req_t delete_tunnel_req; - memset(&delete_tunnel_req, 0, sizeof(delete_tunnel_req)); - delete_tunnel_req.rnti = ctxt->rntiMaybeUEid; - gtpv1u_delete_x2u_tunnel(ctxt->module_id, &delete_tunnel_req); - return true; - } else { - /* data packet */ - LOG_I(GTPU, "Received a message data forwarding length %d\n", sdu_buffer_sizeP); - // Is it from remote ENB - // We have to push it to the UE ? - if(ue_context_p->ue_context.handover_info->state != HO_COMPLETE) { - int result = pdcp_data_req( - ctxt, - srb_flagP, - rb_idP, - muiP, // mui - confirmP, // confirm - sdu_buffer_sizeP, - sdu_buffer_pP, - modeP, - sourceL2Id, - destinationL2Id - ); - ue_context_p->ue_context.handover_info->forwarding_state = FORWARDING_NO_EMPTY; - return result; - } else { /* It is from from epc message */ - /* in the source enb, UE in RRC_HO_EXECUTION mode */ - // ????? - return true; - } - } - } - LOG_E(RRC,"This function should return before the end\n"); - return false; -} - - void rrc_eNB_send_GTPV1U_ENB_DELETE_TUNNEL_REQ( module_id_t enb_mod_idP, rrc_eNB_ue_context_t* ue_context_pP diff --git a/openair2/RRC/LTE/rrc_eNB_GTPV1U.h b/openair2/RRC/LTE/rrc_eNB_GTPV1U.h index 7d1ff07e10..f23f575e9b 100644 --- a/openair2/RRC/LTE/rrc_eNB_GTPV1U.h +++ b/openair2/RRC/LTE/rrc_eNB_GTPV1U.h @@ -54,13 +54,4 @@ void rrc_eNB_send_GTPV1U_ENB_DELETE_TUNNEL_REQ( rrc_eNB_ue_context_t* ue_context_pP ); -bool gtpv_data_req(const protocol_ctxt_t* const ctxt_pP, - const rb_id_t rb_idP, - const mui_t muiP, - const confirm_t confirmP, - const sdu_size_t sdu_sizeP, - uint8_t* const buffer_pP, - const pdcp_transmission_mode_t modeP, - uint32_t task_id); - #endif /* RRC_ENB_GTPV1U_H_ */ From 9a248b441109ba44c1b3d1ebea502f0ea53f74f6 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 12 Feb 2026 06:14:35 +0100 Subject: [PATCH 3/8] Move rrc_eNB_GTPV1U.c from GTP to LTE RRC module in cmake --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index bae1c590bc..ccc5e55d43 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1136,6 +1136,7 @@ set(L2_SRC ${RRC_DIR}/rrc_eNB_M2AP.c ${RRC_DIR}/rrc_eNB_UE_context.c ${NR_RRC_DIR}/rrc_gNB_UE_context.c + ${RRC_DIR}/rrc_eNB_GTPV1U.c ${RRC_DIR}/rrc_common.c ${RRC_DIR}/L2_interface.c ${RRC_DIR}/L2_interface_common.c @@ -1382,7 +1383,6 @@ include_directories("${OPENAIR1_DIR}/SCHED_NR_UE") ########################## add_library (GTPV1U - ${RRC_DIR}/rrc_eNB_GTPV1U.c ${OPENAIR3_DIR}/ocp-gtpu/gtp_itf.cpp ${OPENAIR3_DIR}/ocp-gtpu/gtpu_extensions.c ) From 11c27915270bc154280496ea693a6f76c7497bc3 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 12 Feb 2026 06:14:49 +0100 Subject: [PATCH 4/8] Remove 5G code from LTE RRC --- CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ccc5e55d43..7dee45a453 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1135,7 +1135,6 @@ set(L2_SRC ${RRC_DIR}/rrc_eNB_S1AP.c ${RRC_DIR}/rrc_eNB_M2AP.c ${RRC_DIR}/rrc_eNB_UE_context.c - ${NR_RRC_DIR}/rrc_gNB_UE_context.c ${RRC_DIR}/rrc_eNB_GTPV1U.c ${RRC_DIR}/rrc_common.c ${RRC_DIR}/L2_interface.c From be4461ccca3b9c8868839c8de72850f5f52954bd Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 12 Feb 2026 06:19:19 +0100 Subject: [PATCH 5/8] Move GTPV1U cmake code to subdirectory Move the module cmake code to its subdirectory for better organization, and remove superfluous include_directories() that are not recommended to be used (as it populates all other targets with those directories, most of which don't need it anyway). Slightly clean up the module by removing headers to unused dependencies. The GTP module has a dependency to 4G and 5G RLC, which should not exist (the RLC modules have a dependency on GTP, not the other way around), but in this commit, make as little modifications as possible. --- CMakeLists.txt | 9 --------- openair2/E1AP/CMakeLists.txt | 2 +- openair3/CMakeLists.txt | 1 + openair3/ocp-gtpu/CMakeLists.txt | 4 ++++ openair3/ocp-gtpu/gtp_itf.cpp | 14 +++++++------- 5 files changed, 13 insertions(+), 17 deletions(-) create mode 100644 openair3/ocp-gtpu/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 7dee45a453..ad70a1b53e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -595,7 +595,6 @@ include_directories("${OPENAIR3_DIR}/S1AP") include_directories("${OPENAIR2_DIR}/X2AP") include_directories("${OPENAIR2_DIR}/M2AP") include_directories("${OPENAIR2_DIR}/F1AP") -include_directories("${OPENAIR3_DIR}/ocp-gtpu") include_directories("${OPENAIR3_DIR}/M3AP") include_directories("${OPENAIR3_DIR}/MME_APP") include_directories("${OPENAIR_DIR}/radio/COMMON") @@ -1381,14 +1380,6 @@ include_directories("${OPENAIR1_DIR}/SCHED_NR_UE") # CN libs ########################## -add_library (GTPV1U - ${OPENAIR3_DIR}/ocp-gtpu/gtp_itf.cpp - ${OPENAIR3_DIR}/ocp-gtpu/gtpu_extensions.c - ) -target_link_libraries(GTPV1U PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs) -target_link_libraries(GTPV1U PRIVATE SIMU ds) -include_directories(${OPENAIR3_DIR}/ocp-gtp) - set (MME_APP_SRC ${OPENAIR3_DIR}/MME_APP/mme_app.c ${OPENAIR3_DIR}/MME_APP/mme_config.c diff --git a/openair2/E1AP/CMakeLists.txt b/openair2/E1AP/CMakeLists.txt index eb3bd0f546..39b768ffc3 100644 --- a/openair2/E1AP/CMakeLists.txt +++ b/openair2/E1AP/CMakeLists.txt @@ -4,7 +4,7 @@ add_subdirectory(lib) add_library(e1ap e1ap.c e1ap_common.c) target_link_libraries(e1ap PUBLIC asn1_e1ap e1ap_lib - PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs asn1_f1ap UTIL e1_if) + PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs asn1_f1ap UTIL e1_if GTPV1U) target_include_directories(e1ap PUBLIC ${CMAKE_CURRENT_DIR}) if(ENABLE_TESTS) diff --git a/openair3/CMakeLists.txt b/openair3/CMakeLists.txt index 55f9fe91d6..706d343f56 100644 --- a/openair3/CMakeLists.txt +++ b/openair3/CMakeLists.txt @@ -1,4 +1,5 @@ add_subdirectory(LPP) +add_subdirectory(ocp-gtpu) add_subdirectory(M3AP) add_subdirectory(S1AP) add_subdirectory(NAS) diff --git a/openair3/ocp-gtpu/CMakeLists.txt b/openair3/ocp-gtpu/CMakeLists.txt new file mode 100644 index 0000000000..bcb76e98c9 --- /dev/null +++ b/openair3/ocp-gtpu/CMakeLists.txt @@ -0,0 +1,4 @@ +add_library (GTPV1U gtp_itf.cpp gtpu_extensions.c) +target_link_libraries(GTPV1U PRIVATE asn1_lte_rrc_hdrs asn1_nr_rrc_hdrs) # for ITTI +target_link_libraries(GTPV1U PRIVATE SIMU ds) +target_include_directories(GTPV1U PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/openair3/ocp-gtpu/gtp_itf.cpp b/openair3/ocp-gtpu/gtp_itf.cpp index 6650439e81..9509c5a8a4 100644 --- a/openair3/ocp-gtpu/gtp_itf.cpp +++ b/openair3/ocp-gtpu/gtp_itf.cpp @@ -15,15 +15,15 @@ extern "C" { #include #include "common/utils/LOG/log.h" #include -#include -#include -#include -#include -#include -#include "openair2/SDAP/nr_sdap/nr_sdap.h" -#include "openair3/ocp-gtpu/gtpu_extensions.h" #include "sim.h" +// TODO these dependencies should not exist and be removed +#include "openair2/LAYER2/nr_rlc/nr_rlc_oai_api.h" +#include "openair2/LAYER2/RLC/rlc.h" + +#include "gtp_itf.h" +#include "gtpu_extensions.h" + #pragma pack(1) typedef struct Gtpv1uMsgHeader { From d290850e5987744b5aa438eeb0dabee6e354b8ca Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 12 Feb 2026 08:27:12 +0100 Subject: [PATCH 6/8] Add basic GTP unit test Add a simple unit test that tests sending unidirectional traffic. So far, it merely replicates what is already in nr-cuup-load-test, but it's a single executable (so easier to handle), and could be more easily extended to further tests (e.g., IPv6). A couple of ideas for refactoring/improvements have been added in comments in the test. --- openair3/ocp-gtpu/CMakeLists.txt | 4 + openair3/ocp-gtpu/tests/CMakeLists.txt | 6 + openair3/ocp-gtpu/tests/test_gtp.cpp | 199 +++++++++++++++++++++++++ 3 files changed, 209 insertions(+) create mode 100644 openair3/ocp-gtpu/tests/CMakeLists.txt create mode 100644 openair3/ocp-gtpu/tests/test_gtp.cpp diff --git a/openair3/ocp-gtpu/CMakeLists.txt b/openair3/ocp-gtpu/CMakeLists.txt index bcb76e98c9..37e583aa5d 100644 --- a/openair3/ocp-gtpu/CMakeLists.txt +++ b/openair3/ocp-gtpu/CMakeLists.txt @@ -2,3 +2,7 @@ add_library (GTPV1U gtp_itf.cpp gtpu_extensions.c) target_link_libraries(GTPV1U PRIVATE asn1_lte_rrc_hdrs asn1_nr_rrc_hdrs) # for ITTI target_link_libraries(GTPV1U PRIVATE SIMU ds) target_include_directories(GTPV1U PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +if(ENABLE_TESTS) + add_subdirectory(tests) +endif() diff --git a/openair3/ocp-gtpu/tests/CMakeLists.txt b/openair3/ocp-gtpu/tests/CMakeLists.txt new file mode 100644 index 0000000000..2b6b4d6189 --- /dev/null +++ b/openair3/ocp-gtpu/tests/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(test_gtp test_gtp.cpp) +target_link_libraries(test_gtp PRIVATE GTPV1U GTest::gtest minimal_lib) +# the following dependencies should ideally not exist, but GTP requires them +target_link_libraries(test_gtp PRIVATE ITTI asn1_lte_rrc_hdrs asn1_nr_rrc_hdrs) +add_dependencies(tests test_gtp) +add_test(NAME test_gtp COMMAND ./test_gtp) diff --git a/openair3/ocp-gtpu/tests/test_gtp.cpp b/openair3/ocp-gtpu/tests/test_gtp.cpp new file mode 100644 index 0000000000..6df27a07f2 --- /dev/null +++ b/openair3/ocp-gtpu/tests/test_gtp.cpp @@ -0,0 +1,199 @@ +/* + * 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 +#include + +#include + +#include "common/platform_types.h" +#include "common/utils/LOG/log.h" +#include "common/config/config_userapi.h" + +#include "gtp_itf.h" + +extern "C" { +configmodule_interface_t *uniqCfg; + +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(); +} + +int nr_rlc_get_available_tx_space(const rnti_t rntiP, const logical_chan_id_t channel_idP) +{ + abort(); + return 0; +} +void *get_softmodem_params(void) +{ + abort(); + return NULL; +}; +} + +/* @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); +} + +static in_addr_t get_addr(int ai_family, const char *ip) +{ + AssertFatal(ai_family == AF_INET, "only IPv4 supported\n"); + in_addr_t addr = {0}; + inet_pton(ai_family, ip, &addr); + return addr; +} + +static transport_layer_addr_t get_tl_addr(int ai_family, const char *ip) +{ + in_addr_t addr = get_addr(ai_family, ip); + transport_layer_addr_t tl_addr = {.length = 32}; + memcpy(tl_addr.buffer, &addr, 4); + return tl_addr; +} + +static void run_basic_test(uint32_t ue_id, + long pdu_id, + long qfi, + int num_send, + int *rcv_count, + gtpCallback callBack, + gtpCallbackSDAP callBackSDAP) +{ + /* set up two instances on different IPs */ + const char *ip1 = "127.0.0.1"; + const char *ip2 = "127.0.0.2"; + uint16_t port = 4567; + instance_t ep1 = init_gtp(ip1, port); + EXPECT_GE(ep1, 1); + instance_t ep2 = init_gtp(ip2, port); + EXPECT_GE(ep2, 1); + EXPECT_NE(ep1, ep2); // different IPs => different interfaces + + /* Create receiving end on ep1. Since we don't know the ep2's TEID, we also + * don't provide an address yet, hence "null_addr". Install the callback + * specific to this test. */ + transport_layer_addr_t null_addr = {.length = 32}; + teid_t t1 = newGtpuCreateTunnel(ep1, ue_id, pdu_id, pdu_id, -1, qfi, null_addr, callBack, callBackSDAP); + + /* Create the sending end on ep2. We have ep1's address/TEID, so create the + * remote endpoint. Don't provide a callback, as this is supposed to be + * unidirectional. */ + transport_layer_addr_t tl_addr1 = get_tl_addr(AF_INET, ip1); + teid_t t2 = newGtpuCreateTunnel(ep2, ue_id, pdu_id, pdu_id, t1, qfi, tl_addr1, NULL, NULL); + + EXPECT_NE(t1, t2); // cannot be the same TEIDs + + /* With the TEID at ep2, update ep1 endpoint. */ + in_addr_t addr2 = get_addr(AF_INET, ip2); + GtpuUpdateTunnelOutgoingAddressAndTeid(ep1, ue_id, pdu_id, addr2, t2); + + /* Send num_send messages, with each message three bytes with a specfic + * payload structure (each byte increasing within the buffer and across + * message) checked by the callback recv_basic_conn_qfi(). */ + uint8_t buf[3]; + uint8_t payload_counter = 0; + for (int i = 0; i < num_send; ++i) { + for (size_t p = 0; p < sizeof(buf); p++) + buf[p] = payload_counter++; + gtpv1uSendDirect(ep2, ue_id, pdu_id, buf, sizeof(buf), false, false); + } + + usleep(100 * 1000); // wait 100ms to give time to receive packets + + /* check that three messages have been received. The callback checked that + * the messages had the expected format. */ + EXPECT_EQ(*rcv_count, num_send); + + int ret; + ret = newGtpuDeleteAllTunnels(ep1, ue_id); + EXPECT_EQ(ret, 0); + ret = newGtpuDeleteAllTunnels(ep2, ue_id); + EXPECT_EQ(ret, 0); +} + +static int recv_count_qfi = 0; +static bool recv_basic_conn_qfi(protocol_ctxt_t *ctxt, + const ue_id_t ue_id, + const srb_flag_t flag, + 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) +{ + EXPECT_EQ(ue_id, 1); // as defined in basic_conn_qfi + EXPECT_EQ(qfi, 1); + EXPECT_EQ(size, 3); + for (int i = 0; i < size; ++i) + EXPECT_EQ(buf[i], recv_count_qfi * size + i); + recv_count_qfi++; + LOG_I(GTPU, "received message %d with size 3, content %d.%d.%d\n", recv_count_qfi, buf[0], buf[1], buf[2]); + return true; +} + +/* Test unidirectional GTP message forwarding for a single UE with QFI. */ +TEST(gtp, basic_conn_qfi) +{ + /* we consider only a single UE on a specfic bearer (ID=3) */ + uint32_t ue_id = 1; + long pdu_id = 3; + long qfi = 1; + int num_send = 3; + run_basic_test(ue_id, pdu_id, qfi, num_send, &recv_count_qfi, NULL, recv_basic_conn_qfi); +} + +/* ideas for tests: + * - share IP addresses among two instances (e.g., F1-U/NG-U on same IP/port) + * - support of IPv6 + * - test extension headers + * + * ideas for cleanup: + * - simplify callback, harmonize "normal" and "SDAP" callbacks + * - consistently use either transport_layer_addr_t or in_addr + */ + +int main(int argc, char **argv) +{ + //if ((uniqCfg = load_configmodule(argc, argv, CONFIG_ENABLECMDLINEONLY)) == NULL) { + // exit_fun("[SOFTMODEM] Error, configuration module init failed\n"); + //} + // TODO logInit() without config? + logInit(); + g_log->log_component[GTPU].level = OAILOG_DEBUG; + + testing::InitGoogleTest(&argc, argv); + + return RUN_ALL_TESTS(); +} From d6c732107714369fca08bfad1cb00decfe668fd4 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Mon, 16 Feb 2026 18:40:10 +0100 Subject: [PATCH 7/8] Implement removal of endpoint For unit testing, allow to remove endpoints such that we could reuse a port/IP combination. The function does not take care of endpoints that are still "dangling", it is the user's responsibility to remove all existing TEIDs. --- openair3/ocp-gtpu/gtp_itf.cpp | 14 ++++++++++++++ openair3/ocp-gtpu/gtp_itf.h | 1 + openair3/ocp-gtpu/tests/test_gtp.cpp | 5 +++++ 3 files changed, 20 insertions(+) diff --git a/openair3/ocp-gtpu/gtp_itf.cpp b/openair3/ocp-gtpu/gtp_itf.cpp index 9509c5a8a4..2327d24c2f 100644 --- a/openair3/ocp-gtpu/gtp_itf.cpp +++ b/openair3/ocp-gtpu/gtp_itf.cpp @@ -613,6 +613,20 @@ instance_t gtpv1Init(openAddr_t context) return id; } +/* \brief remove the GTP instance from the list of instances. Does not make an + * attempt to free corresponding TEIDs, as we have many and will simply not + * reuse it later. */ +int gtpv1Term(instance_t instance) +{ + pthread_mutex_lock(&globGtp.gtp_lock); + getInstRetInt(compatInst(instance)); + gtpv1uReceiverCancel(inst->thrData.t); + close(instance); + globGtp.instances.erase(instance); + pthread_mutex_unlock(&globGtp.gtp_lock); + return 0; +} + void GtpuUpdateTunnelOutgoingAddressAndTeid(instance_t instance, ue_id_t ue_id, ebi_t bearer_id, diff --git a/openair3/ocp-gtpu/gtp_itf.h b/openair3/ocp-gtpu/gtp_itf.h index 8139eb3667..0ecddd6f19 100644 --- a/openair3/ocp-gtpu/gtp_itf.h +++ b/openair3/ocp-gtpu/gtp_itf.h @@ -130,6 +130,7 @@ typedef struct gtpv1u_gnb_delete_tunnel_req_s gtpv1u_gnb_delete_tunnel_req_t; size_t len); instance_t gtpv1Init(openAddr_t context); + int gtpv1Term(instance_t inst); void *gtpv1uTask(void *args); #ifdef __cplusplus diff --git a/openair3/ocp-gtpu/tests/test_gtp.cpp b/openair3/ocp-gtpu/tests/test_gtp.cpp index 6df27a07f2..ba2d3d622c 100644 --- a/openair3/ocp-gtpu/tests/test_gtp.cpp +++ b/openair3/ocp-gtpu/tests/test_gtp.cpp @@ -136,6 +136,11 @@ static void run_basic_test(uint32_t ue_id, EXPECT_EQ(ret, 0); ret = newGtpuDeleteAllTunnels(ep2, ue_id); EXPECT_EQ(ret, 0); + + ret = gtpv1Term(ep1); + EXPECT_EQ(ret, 0); + ret = gtpv1Term(ep2); + EXPECT_EQ(ret, 0); } static int recv_count_qfi = 0; From 2a925d6cd40728b8664ee8ce10ece0b7d07128f2 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Mon, 16 Feb 2026 18:50:11 +0100 Subject: [PATCH 8/8] GTP: add second test for tunnels without QFI --- openair3/ocp-gtpu/tests/test_gtp.cpp | 32 ++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/openair3/ocp-gtpu/tests/test_gtp.cpp b/openair3/ocp-gtpu/tests/test_gtp.cpp index ba2d3d622c..cd307df7e9 100644 --- a/openair3/ocp-gtpu/tests/test_gtp.cpp +++ b/openair3/ocp-gtpu/tests/test_gtp.cpp @@ -179,6 +179,38 @@ TEST(gtp, basic_conn_qfi) run_basic_test(ue_id, pdu_id, qfi, num_send, &recv_count_qfi, NULL, recv_basic_conn_qfi); } +static int recv_count = 0; +static bool recv_basic_conn(protocol_ctxt_t *ctxt, + const srb_flag_t srb_flagP, + const rb_id_t rb_idP, + const mui_t muiP, + const confirm_t confirmP, + const sdu_size_t size, + unsigned char *const buf, + const pdcp_transmission_mode_t modeP, + const uint32_t *sourceL2Id, + const uint32_t *destinationL2Id) +{ + EXPECT_EQ(ctxt->rntiMaybeUEid, 12); // as defined in basic_conn + EXPECT_EQ(size, 3); + for (int i = 0; i < size; ++i) + EXPECT_EQ(buf[i], recv_count * size + i); + recv_count++; + LOG_I(GTPU, "received message %d with size 3, content %d.%d.%d\n", recv_count, buf[0], buf[1], buf[2]); + return true; +} + +/* Test unidirectional GTP message forwarding for a single UE (without QFI). */ +TEST(gtp, basic_conn) +{ + /* we consider only a single UE on a specfic bearer (ID=3) */ + uint32_t ue_id = 12; + long pdu_id = 5; + long noqfi = -1; + int num_send = 12; + run_basic_test(ue_id, pdu_id, noqfi, num_send, &recv_count, recv_basic_conn, NULL); +} + /* ideas for tests: * - share IP addresses among two instances (e.g., F1-U/NG-U on same IP/port) * - support of IPv6