The (remote) port number of the GTP module is configured during initialization. It is therefore not required when calling newGtpuCreateTunnel(), and could even lead to bugs (if another port number is used than specified during GTP init).
674 lines
26 KiB
C
674 lines
26 KiB
C
/*
|
|
* 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;
|
|
}
|