From d290850e5987744b5aa438eeb0dabee6e354b8ca Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 12 Feb 2026 08:27:12 +0100 Subject: [PATCH] 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(); +}