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.
This commit is contained in:
@@ -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 asn1_lte_rrc_hdrs asn1_nr_rrc_hdrs) # for ITTI
|
||||||
target_link_libraries(GTPV1U PRIVATE SIMU ds)
|
target_link_libraries(GTPV1U PRIVATE SIMU ds)
|
||||||
target_include_directories(GTPV1U PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
target_include_directories(GTPV1U PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
|
||||||
|
if(ENABLE_TESTS)
|
||||||
|
add_subdirectory(tests)
|
||||||
|
endif()
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -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 <stdlib.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#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();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user