mirror of
https://gitlab.eurecom.fr/oai/openairinterface5g.git
synced 2026-07-13 04:30:28 +00:00
GTPU: move QFI handling to send path and update tunnel API usage
Refactor GTP-U tunnel and send APIs so QFI is handled when sending packets, not stored in tunnel creation state. Update SDAP and CU-UP integration to use PDU-session keyed N3 mappings and explicit QFI-marked sends. This commit clarifies layering ownership: GTP-U stays transport-only (TEID lookup, decapsulation, extension parsing, callback dispatch), while SDAP owns QoS semantics (QFI handling, QoS-flow-to-DRB policy, default DRB behavior, and mapping updates); GTP-U does not perform runtime QFI-to-DRB mapping or synthesize QFI. Changes: - remove `outgoing_qfi` from `gtpv1u_gnb_create_tunnel_req_t` and stop storing QFI as tunnel creation metadata; `newGtpuCreateTunnel(...)` now carries only transport/tunnel identity parameters (incoming_bearer_id, outgoing_bearer_id, outgoing_teid, remote address, callbacks) - add `gtpv1uSendDirectWithQFI()` and pass QFI into `_gtpv1uSendDirect` to build UL PDU Session Container extensions - shift QFI handling from tunnel provisioning to per-packet TX APIs: QFI is passed explicitly only when sending (`gtpv1uSendDirectWithQFI(...)`) and is absent from non-SDAP/F1 sends (`gtpv1uSendDirect(...)`) - align N3 tunnel request semantics with session-level keys by setting incoming_rb_id to PDU session ID on N3 paths, while F1 paths keep DRB ID - keep `gtpv1uSendDirect()` and `gtpv1uSendDirectWithNRUSeqNum()` on `NO_QFI`, and enforce non-SDAP RX callback path only when QFI is absent - update `nr_sdap_rx_entity` to extract/validate QFI from SDAP UL headers, send UL data with `gtpv1uSendDirectWithQFI`, and use non-QFI send when SDAP header is disabled - add disabled-SDAP safety checks in SDAP entity setup/mapping to enforce single-DRB and single-flow constraints per PDU session - extend `test_gtp.cpp` with a `multi_qos_flows` scenario and QFI-aware send calls - update `nr-cuup-load-test.c` bearer setup fields and tunnel creation calls to match the new API - update tests/nr-cuup/nr-cuup-load-test.c to set explicit PDU session and QoS/SDAP parameters (sessionType, qosFlows[0], SDAP header flags), and to migrate both N3 and F1 tunnel creation calls to the new newGtpuCreateTunnel(...) signature (without outgoing_qfi) Signed-off-by: Guido Casati <guido.casati@openairinterface.org>
This commit is contained in:
@@ -143,11 +143,10 @@ typedef struct gtpv1u_gnb_create_tunnel_req_s {
|
||||
ue_id_t ue_id;
|
||||
// Outgoing TEID for the GTP tunnel
|
||||
teid_t outgoing_teid;
|
||||
// Outgoing QFI
|
||||
int outgoing_qfi;
|
||||
// PDU Session ID (1..255) identifies the PDU session
|
||||
int pdusession_id;
|
||||
// Incoming RB
|
||||
// Incoming TEID maps to this bearer ID:
|
||||
// for F1-U, DRB ID; for N3-U, PDU session ID (tunnel key).
|
||||
uint16_t incoming_rb_id;
|
||||
// Destination TL address
|
||||
transport_layer_addr_t dst_addr;
|
||||
|
||||
@@ -63,12 +63,7 @@ static f1ap_up_tnl_t f1_drb_gtpu_create(const gtpv1u_gnb_create_tunnel_req_t *re
|
||||
{
|
||||
f1ap_up_tnl_t out = {0};
|
||||
|
||||
LOG_I(GTPU,
|
||||
"Incoming DRB %d / PDU Session %d - UL TEID %d QFI %d\n",
|
||||
req->incoming_rb_id,
|
||||
req->pdusession_id,
|
||||
req->outgoing_teid,
|
||||
req->outgoing_qfi);
|
||||
LOG_I(GTPU, "Incoming DRB %d / PDU Session %d - UL TEID %d\n", req->incoming_rb_id, req->pdusession_id, req->outgoing_teid);
|
||||
|
||||
instance_t f1inst = get_f1_gtp_instance();
|
||||
DevAssert(f1inst >= 0);
|
||||
@@ -335,12 +330,10 @@ static int handle_ue_context_drbs_setup(NR_UE_info_t *UE,
|
||||
// just put same number of tunnels in DL as in UL
|
||||
DevAssert(drb->up_ul_tnl_len == 1);
|
||||
resp_drb->up_dl_tnl_len = drb->up_ul_tnl_len;
|
||||
|
||||
if (f1inst >= 0) { // we actually use F1-U
|
||||
// F1-U tunnel setup: 1 GTP-U tunnel per DRB
|
||||
gtpv1u_gnb_create_tunnel_req_t req = {.ue_id = UE->rnti,
|
||||
.outgoing_teid = drb->up_ul_tnl[0].teid,
|
||||
.outgoing_qfi = -1, // don't put PDU session marker in F1 GTP
|
||||
.pdusession_id = drb->id,
|
||||
.incoming_rb_id = drb->id,
|
||||
.dst_addr.length = 32};
|
||||
|
||||
@@ -93,12 +93,7 @@ static UP_TL_information_t f1_drb_gtpu_create(const instance_t f1inst,
|
||||
{
|
||||
UP_TL_information_t out = {0};
|
||||
|
||||
LOG_I(GTPU,
|
||||
"Incoming DRB %d / PDU Session %d - UL TEID %d QFI %d\n",
|
||||
req->incoming_rb_id,
|
||||
req->pdusession_id,
|
||||
req->outgoing_teid,
|
||||
req->outgoing_qfi);
|
||||
LOG_I(GTPU, "Incoming DRB %d / PDU Session %d - UL TEID %d\n", req->incoming_rb_id, req->pdusession_id, req->outgoing_teid);
|
||||
|
||||
gtpv1u_gnb_create_tunnel_resp_t resp = {0};
|
||||
int ret = gtpv1u_create_ngu_tunnel(f1inst, req, &resp, cu_f1u_data_req, NULL);
|
||||
@@ -126,12 +121,7 @@ static UP_TL_information_t n3_gtpu_create(const gtpv1u_gnb_create_tunnel_req_t *
|
||||
instance_t n3inst = get_n3_gtp_instance();
|
||||
UP_TL_information_t out = {0};
|
||||
|
||||
LOG_I(GTPU,
|
||||
"Incoming DRB %d / PDU Session %d - UL TEID %d QFI %d\n",
|
||||
req->incoming_rb_id,
|
||||
req->pdusession_id,
|
||||
req->outgoing_teid,
|
||||
req->outgoing_qfi);
|
||||
LOG_I(GTPU, "N3 GTP-U tunnel: PDUSession=%d/UL TEID=0x%08x\n", req->pdusession_id, req->outgoing_teid);
|
||||
|
||||
gtpv1u_gnb_create_tunnel_resp_t resp = {0};
|
||||
int ret = gtpv1u_create_ngu_tunnel(n3inst, req, &resp, nr_pdcp_data_req_drb, sdap_data_req);
|
||||
@@ -257,7 +247,6 @@ void e1_bearer_context_setup(const e1ap_bearer_setup_req_t *req)
|
||||
teid_t dummy_teid = 0xffff; // we will update later with answer from DU
|
||||
in_addr_t dummy_address = {0}; // IPv4, updated later with answer from DU
|
||||
gtpv1u_gnb_create_tunnel_req_t f1_tunnel_req = {.incoming_rb_id = req_drb->id,
|
||||
.outgoing_qfi = -1, // don't put PDU session marker in GTP
|
||||
.outgoing_teid = dummy_teid,
|
||||
.pdusession_id = req_drb->id,
|
||||
.ue_id = cu_up_ue_id,
|
||||
@@ -282,16 +271,13 @@ void e1_bearer_context_setup(const e1ap_bearer_setup_req_t *req)
|
||||
e1_add_bearers(cu_up_ue_id, &DRB_configList, &security_parameters);
|
||||
ASN_STRUCT_RESET(asn_DEF_NR_DRB_ToAddModList, &DRB_configList.list);
|
||||
|
||||
/** GTP tunnel for N3/to core: one GTP-U tunnel per PDU session
|
||||
* which can contains multiple QoS Flows
|
||||
* @note Mapping only the default QFI (by implementation) */
|
||||
/** GTP tunnel for N3/to core: one GTP-U tunnel per PDU session. */
|
||||
LOG_D(E1AP, "In %s: GTP tunnel for N3/to core for PDU Session %ld\n", __func__, req_pdu->sessionId);
|
||||
// N3 GTP-U Tunnel Request for PDU Session
|
||||
gtpv1u_gnb_create_tunnel_req_t n3_tunnel_req = {.ue_id = cu_up_ue_id,
|
||||
.outgoing_teid = req_pdu->UP_TL_information.teId,
|
||||
.outgoing_qfi = req_pdu->DRBnGRanList[0].qosFlows[0].qfi, // one QFI, use as marking
|
||||
.pdusession_id = req_pdu->sessionId,
|
||||
.incoming_rb_id = req_pdu->DRBnGRanList[0].id,
|
||||
.incoming_rb_id = req_pdu->sessionId,
|
||||
.dst_addr.length = 32};
|
||||
memcpy(&n3_tunnel_req.dst_addr.buffer, &req_pdu->UP_TL_information.tlAddress, sizeof(uint8_t) * 4); // only IPv4 now
|
||||
resp_pdu->tl_info = n3_gtpu_create(&n3_tunnel_req);
|
||||
|
||||
@@ -333,7 +333,6 @@ void rrc_add_nsa_user(gNB_RRC_INST *rrc, x2ap_ENDC_sgnb_addition_req_t *m, sctp_
|
||||
.incoming_rb_id = drb_id,
|
||||
.pdusession_id = drb_id,
|
||||
.outgoing_teid = 0xffff, // will be updated later
|
||||
.outgoing_qfi = -1, // don't put PDU session marker in F1 GTP
|
||||
.dst_addr.length = 32,
|
||||
};
|
||||
gtpv1u_gnb_create_tunnel_resp_t resp = {0};
|
||||
|
||||
@@ -48,19 +48,18 @@ bool sdap_data_req(protocol_ctxt_t *ctxt_p,
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool ret = sdap_entity->tx_entity(sdap_entity,
|
||||
ctxt_p,
|
||||
srb_flag,
|
||||
mui,
|
||||
confirm,
|
||||
sdu_buffer_size,
|
||||
sdu_buffer,
|
||||
pt_mode,
|
||||
sourceL2Id,
|
||||
destinationL2Id,
|
||||
qfi,
|
||||
rqi);
|
||||
return ret;
|
||||
return sdap_entity->tx_entity(sdap_entity,
|
||||
ctxt_p,
|
||||
srb_flag,
|
||||
mui,
|
||||
confirm,
|
||||
sdu_buffer_size,
|
||||
sdu_buffer,
|
||||
pt_mode,
|
||||
sourceL2Id,
|
||||
destinationL2Id,
|
||||
qfi,
|
||||
rqi);
|
||||
}
|
||||
|
||||
void sdap_data_ind(int pdcp_entity, int is_gnb, int pdusession_id, ue_id_t ue_id, char *buf, int size)
|
||||
|
||||
@@ -191,13 +191,16 @@ static void nr_sdap_rx_entity(nr_sdap_entity_t *entity,
|
||||
char *buf,
|
||||
int size)
|
||||
{
|
||||
/* The offset of the SDAP header, it might be 0 if has_sdap_rx is not true in the pdcp entity. */
|
||||
int offset=0;
|
||||
bool sdap_ul_rx = false;
|
||||
bool sdap_dl_rx = false;
|
||||
int qfi = -1;
|
||||
/* If SDAP header is disabled for this entity, bypass header parsing */
|
||||
if (entity->enable_sdap) {
|
||||
uint8_t qfi = buf[0] & 0x3F; // QFI is always the first 6 bits in the first octet
|
||||
/** Extract QFI from SDAP header for UL direction
|
||||
* Per TS 37.324 §6.2.2, QFI is carried in SDAP header when SDAP header is present
|
||||
* This QFI will be used in GTP-U extension header (PDU Session Container)
|
||||
* for N3-U tunnel (the first 6 bits in the first octet) */
|
||||
qfi = buf[0] & 0x3F;
|
||||
if (qfi >= SDAP_MAX_QFI) {
|
||||
LOG_E(SDAP, "Invalid QFI %d received in SDAP header\n", qfi);
|
||||
return;
|
||||
@@ -209,10 +212,14 @@ static void nr_sdap_rx_entity(nr_sdap_entity_t *entity,
|
||||
|
||||
|
||||
if (is_gnb) { // gNB
|
||||
/** QFI extracted from SDAP header for UL direction (N3-U tunnel)
|
||||
* QFI range: 0..63 (6 bits) */
|
||||
if (sdap_ul_rx) { // UL Data/Control PDU with SDAP header
|
||||
offset = SDAP_HDR_LENGTH;
|
||||
DevAssert(qfi >= 0 && qfi < SDAP_MAX_QFI);
|
||||
int offset = SDAP_HDR_LENGTH;
|
||||
nr_sdap_ul_hdr_t *sdap_hdr = (nr_sdap_ul_hdr_t *)buf;
|
||||
LOG_D(SDAP, "RX Entity Received QFI: %u\n", sdap_hdr->QFI);
|
||||
DevAssert(sdap_hdr->QFI == qfi);
|
||||
LOG_D(SDAP, "RX Entity Received QFI: %u\n", qfi);
|
||||
LOG_D(SDAP, "RX Entity Received R bit: %u\n", sdap_hdr->R);
|
||||
LOG_D(SDAP, "RX Entity Received DC bit: %u\n", sdap_hdr->DC);
|
||||
|
||||
@@ -225,22 +232,32 @@ static void nr_sdap_rx_entity(nr_sdap_entity_t *entity,
|
||||
LOG_D(SDAP, "RX Entity Received SDAP Control PDU\n");
|
||||
break;
|
||||
}
|
||||
// Pushing SDAP SDU to GTP-U Layer
|
||||
size_t gtp_len = size - offset;
|
||||
uint8_t *gtp_buf = (uint8_t *)buf + offset;
|
||||
LOG_D(SDAP,
|
||||
"UL SDAP->GTP: ue=%lu pdu_session=%d qfi=%u payload_len=%zu\n",
|
||||
ue_id,
|
||||
pdusession_id,
|
||||
qfi,
|
||||
gtp_len);
|
||||
/** Send to GTP-U with QFI marking for N3-U tunnel
|
||||
* Per TS 29.281 §5.2, QFI is carried in PDU Session Container extension header
|
||||
* For N3-U tunnels (qfi>=0), QFI is added to extension header for UL routing at UPF */
|
||||
gtpv1uSendDirectWithQFI(*N3GTPUInst, ue_id, pdusession_id, qfi, gtp_buf, gtp_len);
|
||||
} else {
|
||||
/* N3-U tunnel with no SDAP header: no QFI marking */
|
||||
DevAssert(qfi == -1);
|
||||
LOG_D(SDAP, "UL SDAP->GTP: ue=%lu pdu_session=%d payload_len=%d\n", ue_id, pdusession_id, size);
|
||||
gtpv1uSendDirect(*N3GTPUInst, ue_id, pdusession_id, (uint8_t *)buf, size, false, false);
|
||||
}
|
||||
|
||||
uint8_t *gtp_buf = (uint8_t *)(buf + offset);
|
||||
size_t gtp_len = size - offset;
|
||||
|
||||
// Pushing SDAP SDU to GTP-U Layer
|
||||
LOG_D(SDAP, "sending message to gtp size %ld\n", gtp_len);
|
||||
// very very dirty hack gloabl var N3GTPUInst
|
||||
instance_t inst = *N3GTPUInst;
|
||||
gtpv1uSendDirect(inst, ue_id, pdusession_id, gtp_buf, gtp_len, false, false);
|
||||
} else { //nrUE
|
||||
/*
|
||||
* TS 37.324 5.2 Data transfer
|
||||
* 5.2.2 Downlink
|
||||
* if the DRB from which this SDAP data PDU is received is configured by RRC with the presence of SDAP header.
|
||||
*/
|
||||
int offset = 0;
|
||||
if (sdap_dl_rx) { // DL Data/Control PDU with SDAP header
|
||||
offset = SDAP_HDR_LENGTH;
|
||||
/*
|
||||
@@ -461,6 +478,19 @@ static void nr_sdap_rm_qos_flows_from_drb(nr_sdap_entity_t *entity, const sdap_c
|
||||
* @param drb the DRB ID to be mapped */
|
||||
static void nr_sdap_qfi2drb_map_update(nr_sdap_entity_t *entity, const sdap_config_t *sdap)
|
||||
{
|
||||
/* disabled SDAP: only one DRB per PDU session existing qfi2drb_table entries must match the DRB to be updated */
|
||||
if (!entity->enable_sdap) {
|
||||
for (int i = 0; i < SDAP_MAX_QFI; i++) {
|
||||
int mapped = entity->qfi2drb_table[i].drb_id;
|
||||
if (mapped != SDAP_NO_MAPPING_RULE) {
|
||||
AssertFatal(mapped == sdap->drb_id,
|
||||
"PDU session %d: disabled SDAP allows only one DRB per PDU session (qfi2drb_table[%d] maps to DRB %d)\n",
|
||||
entity->pdusession_id,
|
||||
i,
|
||||
mapped);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!entity->is_gnb) { // UE control PDU configuration
|
||||
nr_sdap_ue_control_pdu_config(entity, entity->ue_id, sdap);
|
||||
}
|
||||
@@ -507,6 +537,12 @@ static void nr_sdap_add_entity(const int is_gnb, const ue_id_t ue_id, const sdap
|
||||
LOG_I(SDAP, "Default DRB for the created SDAP entity: DRB %d \n", sdap_entity->default_drb);
|
||||
}
|
||||
|
||||
if (!sdap_entity->enable_sdap) {
|
||||
AssertFatal(sdap->mappedQFIs2AddCount <= 1,
|
||||
"PDU session %d: disabled SDAP allows at most one QoS flow to add at entity creation\n",
|
||||
sdap->pdusession_id);
|
||||
}
|
||||
|
||||
// Add QoS flows to the DRB (initial configuration)
|
||||
nr_sdap_add_qos_flows_to_drb(sdap_entity, sdap);
|
||||
|
||||
|
||||
@@ -109,7 +109,11 @@ typedef struct Gtpv1uExtHeader {
|
||||
#define GTP_END_MARKER (254)
|
||||
#define GTP_GPDU (255)
|
||||
|
||||
// GTP bearer context: for sending data
|
||||
/** NO_QFI: indicates no QFI marking (F1-U tunnel or N3-U tunnel with no SDAP header)
|
||||
* Used when there is no UL PDU Session Information (SDAP header) present */
|
||||
#define NO_QFI (-1)
|
||||
|
||||
/** GTP bearer context: for sending data */
|
||||
typedef struct gtpv1u_bearer_s {
|
||||
int sock_fd;
|
||||
struct sockaddr_storage ip;
|
||||
@@ -118,7 +122,6 @@ typedef struct gtpv1u_bearer_s {
|
||||
uint16_t seqNum;
|
||||
uint8_t npduNum;
|
||||
int32_t nru_sequence_number;
|
||||
int outgoing_qfi;
|
||||
} gtpv1u_bearer_t;
|
||||
|
||||
typedef struct {
|
||||
@@ -127,11 +130,15 @@ typedef struct {
|
||||
|
||||
typedef struct {
|
||||
ue_id_t ue_id;
|
||||
ebi_t incoming_rb_id;
|
||||
/** Incoming TEID mapping key:
|
||||
* - F1-U: DRB ID (direct TEID-to-DRB routing on non-SDAP callback path)
|
||||
* - N3-U: PDU session ID (TEID-to-PDU session; SDAP callback then resolves QFI-to-DRB) */
|
||||
uint16_t incoming_rb_id;
|
||||
gtpCallback callBack;
|
||||
teid_t outgoing_teid;
|
||||
gtpCallbackSDAP callBackSDAP;
|
||||
int pdusession_id;
|
||||
/** PDU Session ID (1..255) */
|
||||
uint16_t pdusession_id;
|
||||
} ueidData_t;
|
||||
|
||||
typedef struct {
|
||||
@@ -314,9 +321,13 @@ static int gtpv1uCreateAndSendMsg(gtpv1u_bearer_t *bearer,
|
||||
return !GTPNOK;
|
||||
}
|
||||
|
||||
/** Internal function to send GTP-U packet with optional QFI marking
|
||||
* Per TS 29.281 §5.2, QFI is carried in PDU Session Container extension header for N3-U
|
||||
* @param qfi QoS Flow Identifier (0..63) for N3-U, or NO_QFI (-1) for F1-U */
|
||||
static void _gtpv1uSendDirect(instance_t instance,
|
||||
ue_id_t ue_id,
|
||||
int bearer_id,
|
||||
int qfi,
|
||||
uint8_t *buf,
|
||||
size_t len,
|
||||
bool seqNumFlag,
|
||||
@@ -357,11 +368,10 @@ static void _gtpv1uSendDirect(instance_t instance,
|
||||
|
||||
int extension_count = 0;
|
||||
gtpu_extension_header_t ext[2];
|
||||
if (bearer.outgoing_qfi != -1) {
|
||||
/* 29.281 Figure 5.2.1-3 note 4 says PDU Session Container must come first.
|
||||
* GTPU_EXT_UL_PDU_SESSION_INFORMATION is within a PDU Session Container
|
||||
* so it must be put before any other extension.
|
||||
*/
|
||||
/** Add PDU Session Container extension header if QFI is present (N3-U tunnel)
|
||||
* Per TS 29.281 Figure 5.2.1-3 note 4, PDU Session Container must be the first Extension Header
|
||||
* Per TS 29.281 §5.2, QFI is carried in UL PDU Session Information IE for N3-U */
|
||||
if (qfi != NO_QFI) {
|
||||
ext[extension_count] = {
|
||||
.type = GTPU_EXT_UL_PDU_SESSION_INFORMATION,
|
||||
.ul_pdu_session_information = {
|
||||
@@ -371,10 +381,16 @@ static void _gtpv1uSendDirect(instance_t instance,
|
||||
.snp = false,
|
||||
.n3n9_delay_ind = false,
|
||||
.new_ie_flag = false,
|
||||
.qfi = bearer.outgoing_qfi
|
||||
.qfi = qfi,
|
||||
}
|
||||
};
|
||||
extension_count++;
|
||||
LOG_D(GTPU,
|
||||
"UL TX: Adding PDU Session Container with QFI=%d (ue=%ld bearer_id=%d outgoing_teid=0x%x)\n",
|
||||
qfi,
|
||||
ue_id,
|
||||
bearer_id,
|
||||
bearer.teid_outgoing);
|
||||
}
|
||||
|
||||
if (nru_seqnum != -1) {
|
||||
@@ -406,6 +422,21 @@ static void _gtpv1uSendDirect(instance_t instance,
|
||||
extension_count);
|
||||
}
|
||||
|
||||
/** Send GTP-U packet with QFI marking for N3-U tunnel
|
||||
* Per TS 29.281 §5.2, QFI is carried in PDU Session Container extension header
|
||||
* Used by SDAP layer when forwarding UL packets to N3-U tunnel
|
||||
* @param qfi QoS Flow Identifier (0..63) extracted from SDAP header */
|
||||
void gtpv1uSendDirectWithQFI(instance_t instance, ue_id_t ue_id, int bearer_id, int qfi, uint8_t *buf, size_t len)
|
||||
{
|
||||
AssertFatal(qfi >= 0 && qfi < MAX_QOS_FLOWS,
|
||||
"Invalid QFI %d for gtpv1uSendDirectWithQFI (expected 0..%d)\n",
|
||||
qfi,
|
||||
MAX_QOS_FLOWS - 1);
|
||||
_gtpv1uSendDirect(instance, ue_id, bearer_id, qfi, buf, len, false, false, -1);
|
||||
}
|
||||
|
||||
/** Send GTP-U packet with no QFI marking for F1-U tunnel
|
||||
* @note qfi is set to NO_QFI (-1) for F1-U */
|
||||
void gtpv1uSendDirect(instance_t instance,
|
||||
ue_id_t ue_id,
|
||||
int bearer_id,
|
||||
@@ -414,14 +445,7 @@ void gtpv1uSendDirect(instance_t instance,
|
||||
bool seqNumFlag,
|
||||
bool npduNumFlag)
|
||||
{
|
||||
_gtpv1uSendDirect(instance,
|
||||
ue_id,
|
||||
bearer_id,
|
||||
buf,
|
||||
len,
|
||||
seqNumFlag,
|
||||
npduNumFlag,
|
||||
-1);
|
||||
_gtpv1uSendDirect(instance, ue_id, bearer_id, NO_QFI, buf, len, seqNumFlag, npduNumFlag, -1);
|
||||
}
|
||||
|
||||
void gtpv1uSendDirectWithNRUSeqNum(instance_t instance,
|
||||
@@ -447,14 +471,7 @@ void gtpv1uSendDirectWithNRUSeqNum(instance_t instance,
|
||||
|
||||
pthread_mutex_unlock(&globGtp.gtp_lock);
|
||||
|
||||
_gtpv1uSendDirect(instance,
|
||||
ue_id,
|
||||
bearer_id,
|
||||
buf,
|
||||
len,
|
||||
false,
|
||||
false,
|
||||
nru_seqnum);
|
||||
_gtpv1uSendDirect(instance, ue_id, bearer_id, NO_QFI, buf, len, false, false, nru_seqnum);
|
||||
}
|
||||
|
||||
static void fillDlDeliveryStatusReport(gtpu_extension_header_t *ext, uint32_t RLC_buffer_availability, uint32_t NR_PDCP_PDU_SN)
|
||||
@@ -677,7 +694,6 @@ teid_t newGtpuCreateTunnel(instance_t instance,
|
||||
int incoming_bearer_id,
|
||||
int outgoing_bearer_id,
|
||||
teid_t outgoing_teid,
|
||||
int outgoing_qfi,
|
||||
transport_layer_addr_t remoteAddr,
|
||||
gtpCallback callBack,
|
||||
gtpCallbackSDAP callBackSDAP)
|
||||
@@ -709,7 +725,6 @@ teid_t newGtpuCreateTunnel(instance_t instance,
|
||||
.sock_fd = (int) compatInst(instance), // avoid warning on narrowing conversion: instance is long, sock_fd is int
|
||||
.teid_incoming = incoming_teid,
|
||||
.teid_outgoing = outgoing_teid,
|
||||
.outgoing_qfi = outgoing_qfi,
|
||||
};
|
||||
|
||||
int addrs_length_in_bytes = remoteAddr.length / 8;
|
||||
@@ -778,7 +793,6 @@ int gtpv1u_create_s1u_tunnel(instance_t instance,
|
||||
incoming_rb_id,
|
||||
create_tunnel_req->eps_bearer_id[i],
|
||||
create_tunnel_req->sgw_S1u_teid[i],
|
||||
-1, // no pdu session in 4G
|
||||
create_tunnel_req->sgw_addr[i],
|
||||
callBack,
|
||||
NULL);
|
||||
@@ -858,7 +872,6 @@ int gtpv1u_create_ngu_tunnel(const instance_t instance,
|
||||
create_tunnel_req->incoming_rb_id,
|
||||
create_tunnel_req->pdusession_id,
|
||||
create_tunnel_req->outgoing_teid,
|
||||
create_tunnel_req->outgoing_qfi,
|
||||
create_tunnel_req->dst_addr,
|
||||
callBack,
|
||||
callBackSDAP);
|
||||
@@ -1220,7 +1233,7 @@ static int Gtpv1uHandleGpdu(int h, uint8_t *msgBuf, uint32_t msgBufLen, const st
|
||||
// manyother attributes may come from create tunnel
|
||||
protocol_ctxt_t ctxt = { .enb_flag = 1, .rntiMaybeUEid = uedata.ue_id, };
|
||||
const srb_flag_t srb_flag = SRB_FLAG_NO;
|
||||
const rb_id_t rb_id = uedata.incoming_rb_id;
|
||||
uint16_t rb_id = uedata.incoming_rb_id;
|
||||
const mui_t mui = RLC_MUI_UNDEFINED;
|
||||
const confirm_t confirm = RLC_SDU_CONFIRM_NO;
|
||||
const sdu_size_t sdu_buffer_size = msgBufLen - offset;
|
||||
@@ -1230,7 +1243,7 @@ static int Gtpv1uHandleGpdu(int h, uint8_t *msgBuf, uint32_t msgBufLen, const st
|
||||
const uint32_t destinationL2Id = 0;
|
||||
|
||||
if (sdu_buffer_size > 0) {
|
||||
if (qfi != -1 && uedata.callBackSDAP) {
|
||||
if (qfi != NO_QFI && uedata.callBackSDAP) {
|
||||
if (!uedata.callBackSDAP(&ctxt,
|
||||
uedata.ue_id,
|
||||
srb_flag,
|
||||
@@ -1244,14 +1257,23 @@ static int Gtpv1uHandleGpdu(int h, uint8_t *msgBuf, uint32_t msgBufLen, const st
|
||||
qfi,
|
||||
rqi,
|
||||
uedata.pdusession_id))
|
||||
LOG_E(GTPU, "[%d] down layer refused incoming packet\n", h);
|
||||
LOG_E(GTPU, "[%d] down layer refused incoming SDAP packet\n", h);
|
||||
} else {
|
||||
/* Non-SDAP callback path: direct TEID-to-incoming_rb_id delivery via callBack.
|
||||
* QFI must be absent on this path */
|
||||
AssertFatal(qfi == NO_QFI,
|
||||
"[%d] Non-SDAP callback configured but QFI=%d is present (ue=%lu teid=0x%x)\n",
|
||||
h,
|
||||
qfi,
|
||||
uedata.ue_id,
|
||||
ntohl(msgHdr->teid));
|
||||
if (!uedata.callBack(&ctxt, srb_flag, rb_id, mui, confirm, sdu_buffer_size, sdu_buffer, mode, &sourceL2Id, &destinationL2Id))
|
||||
LOG_E(GTPU, "[%d] down layer refused incoming packet\n", h);
|
||||
}
|
||||
}
|
||||
|
||||
if (NR_PDCP_PDU_SN > 0 && NR_PDCP_PDU_SN % 5 == 0) {
|
||||
/* Delivery status report path uses DRB-based RLC state: keep it on non-SDAP path only. */
|
||||
if (!uedata.callBackSDAP && NR_PDCP_PDU_SN > 0 && NR_PDCP_PDU_SN % 5 == 0) {
|
||||
LOG_D(GTPU, "Create and send DL DATA Delivery status for the previously received PDU, NR_PDCP_PDU_SN: %u \n", NR_PDCP_PDU_SN);
|
||||
int rlc_tx_buffer_space = nr_rlc_get_available_tx_space(ctxt.rntiMaybeUEid, rb_id + 3);
|
||||
LOG_D(GTPU, "Available buffer size in RLC for Tx: %d \n", rlc_tx_buffer_space);
|
||||
|
||||
@@ -110,9 +110,8 @@ typedef struct gtpv1u_gnb_delete_tunnel_req_s gtpv1u_gnb_delete_tunnel_req_t;
|
||||
teid_t newGtpuCreateTunnel(instance_t instance,
|
||||
ue_id_t ue_id,
|
||||
int incoming_bearer_id,
|
||||
int outgoing_rb_id,
|
||||
teid_t teid,
|
||||
int outgoing_qfi,
|
||||
int outgoing_bearer_id,
|
||||
teid_t outgoing_teid,
|
||||
transport_layer_addr_t remoteAddr,
|
||||
gtpCallback callBack,
|
||||
gtpCallbackSDAP callBackSDAP);
|
||||
@@ -127,6 +126,8 @@ typedef struct gtpv1u_gnb_delete_tunnel_req_s gtpv1u_gnb_delete_tunnel_req_t;
|
||||
int newGtpuDeleteAllTunnels(instance_t instance, ue_id_t ue_id);
|
||||
|
||||
void gtpv1uSendDirect(instance_t instance, ue_id_t ue_id, int bearer_id, uint8_t *buf, size_t len, bool seqNumFlag, bool npduNumFlag);
|
||||
void gtpv1uSendDirectWithQFI(instance_t instance, ue_id_t ue_id, int bearer_id, int qfi, uint8_t *buf, size_t len);
|
||||
|
||||
void gtpv1uSendDirectWithNRUSeqNum(instance_t instance,
|
||||
ue_id_t ue_id,
|
||||
int bearer_id,
|
||||
|
||||
@@ -83,13 +83,13 @@ static void run_basic_test(uint32_t ue_id,
|
||||
* 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);
|
||||
teid_t t1 = newGtpuCreateTunnel(ep1, ue_id, pdu_id, pdu_id, -1, 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);
|
||||
teid_t t2 = newGtpuCreateTunnel(ep2, ue_id, pdu_id, pdu_id, t1, tl_addr1, NULL, NULL);
|
||||
|
||||
EXPECT_NE(t1, t2); // cannot be the same TEIDs
|
||||
|
||||
@@ -105,7 +105,10 @@ static void run_basic_test(uint32_t ue_id,
|
||||
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);
|
||||
if (qfi >= 0)
|
||||
gtpv1uSendDirectWithQFI(ep2, ue_id, pdu_id, qfi, buf, sizeof(buf));
|
||||
else
|
||||
gtpv1uSendDirect(ep2, ue_id, pdu_id, buf, sizeof(buf), false, false);
|
||||
}
|
||||
|
||||
usleep(100 * 1000); // wait 100ms to give time to receive packets
|
||||
@@ -151,6 +154,77 @@ static bool recv_basic_conn_qfi(protocol_ctxt_t *ctxt,
|
||||
return true;
|
||||
}
|
||||
|
||||
static int recv_count_multi_qfi = 0;
|
||||
static const uint8_t expected_qfis_multi_qfi[] = {1, 5, 9, 5, 1};
|
||||
static const int expected_qfis_multi_qfi_count = sizeof(expected_qfis_multi_qfi) / sizeof(expected_qfis_multi_qfi[0]);
|
||||
static bool recv_multi_qfi_same_pdu(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, 7U);
|
||||
EXPECT_EQ(pdusession_id, 9);
|
||||
EXPECT_LT(recv_count_multi_qfi, expected_qfis_multi_qfi_count);
|
||||
EXPECT_EQ(qfi, expected_qfis_multi_qfi[recv_count_multi_qfi]);
|
||||
EXPECT_EQ(size, 3);
|
||||
for (int i = 0; i < size; ++i)
|
||||
EXPECT_EQ(buf[i], recv_count_multi_qfi * size + i);
|
||||
recv_count_multi_qfi++;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void run_multi_qos_flows_test(uint32_t ue_id, long pdu_id, const uint8_t *qfis, int num_send)
|
||||
{
|
||||
/* 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);
|
||||
|
||||
transport_layer_addr_t null_addr = {.length = 32};
|
||||
teid_t t1 = newGtpuCreateTunnel(ep1, ue_id, pdu_id, pdu_id, -1, null_addr, NULL, recv_multi_qfi_same_pdu);
|
||||
|
||||
transport_layer_addr_t tl_addr1 = get_tl_addr(AF_INET, ip1);
|
||||
teid_t t2 = newGtpuCreateTunnel(ep2, ue_id, pdu_id, pdu_id, t1, tl_addr1, NULL, NULL);
|
||||
EXPECT_NE(t1, t2);
|
||||
|
||||
in_addr_t addr2 = get_addr(AF_INET, ip2);
|
||||
GtpuUpdateTunnelOutgoingAddressAndTeid(ep1, ue_id, pdu_id, addr2, t2);
|
||||
|
||||
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++;
|
||||
gtpv1uSendDirectWithQFI(ep2, ue_id, pdu_id, qfis[i], buf, sizeof(buf));
|
||||
}
|
||||
|
||||
usleep(100 * 1000);
|
||||
EXPECT_EQ(recv_count_multi_qfi, num_send);
|
||||
|
||||
int ret = newGtpuDeleteAllTunnels(ep1, 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);
|
||||
}
|
||||
|
||||
/* Test unidirectional GTP message forwarding for a single UE with QFI. */
|
||||
TEST(gtp, basic_conn_qfi)
|
||||
{
|
||||
@@ -162,6 +236,16 @@ TEST(gtp, basic_conn_qfi)
|
||||
run_basic_test(ue_id, pdu_id, qfi, num_send, &recv_count_qfi, NULL, recv_basic_conn_qfi);
|
||||
}
|
||||
|
||||
/* Test one PDU session carrying multiple QoS flows (different QFIs). */
|
||||
TEST(gtp, multi_qos_flows)
|
||||
{
|
||||
uint32_t ue_id = 7;
|
||||
long pdu_id = 9;
|
||||
static const uint8_t qfis[] = {1, 5, 9, 5, 1};
|
||||
recv_count_multi_qfi = 0;
|
||||
run_multi_qos_flows_test(ue_id, pdu_id, qfis, sizeof(qfis) / sizeof(qfis[0]));
|
||||
}
|
||||
|
||||
static int recv_count = 0;
|
||||
static bool recv_basic_conn(protocol_ctxt_t *ctxt,
|
||||
const srb_flag_t srb_flagP,
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
|
||||
configmodule_interface_t *uniqCfg;
|
||||
|
||||
/** QFI for NG-U GTP PDU Session Container */
|
||||
#define TEST_QOS_FLOW_ID 1
|
||||
|
||||
#define DL 0
|
||||
#define UL 1
|
||||
|
||||
@@ -101,6 +104,7 @@ static e1ap_bearer_setup_req_t get_breq(uint32_t ue_id, long pdu_id, long drb_id
|
||||
bearer_req.pduSession = calloc_or_fail(1, sizeof(*bearer_req.pduSession));
|
||||
pdu_session_to_setup_t *pdu = &bearer_req.pduSession[0];
|
||||
pdu->sessionId = pdu_id;
|
||||
pdu->sessionType = E1AP_PDU_Session_Type_ipv4;
|
||||
pdu->nssai = *nssai;
|
||||
pdu->securityIndication.integrityProtectionIndication = SECURITY_NOT_NEEDED;
|
||||
pdu->securityIndication.confidentialityProtectionIndication = SECURITY_NOT_NEEDED;
|
||||
@@ -109,6 +113,18 @@ static e1ap_bearer_setup_req_t get_breq(uint32_t ue_id, long pdu_id, long drb_id
|
||||
DRB_nGRAN_to_setup_t *drb = &pdu->DRBnGRanList[0];
|
||||
drb->id = drb_id;
|
||||
drb->numQosFlow2Setup = 1;
|
||||
drb->qosFlows[0] = (qos_flow_to_setup_t){
|
||||
.qfi = TEST_QOS_FLOW_ID,
|
||||
.qos_params.alloc_reten_priority.priority_level = E1AP_PriorityLevel_highest,
|
||||
.qos_params.alloc_reten_priority.preemption_capability = E1AP_Pre_emptionCapability_shall_not_trigger_pre_emption,
|
||||
.qos_params.alloc_reten_priority.preemption_vulnerability = E1AP_Pre_emptionVulnerability_not_pre_emptable,
|
||||
.qos_params.qos_characteristics.qos_type = NON_DYNAMIC,
|
||||
.qos_params.qos_characteristics.non_dynamic.fiveqi = 9,
|
||||
};
|
||||
/* Enable SDAP headers: tests QFI-aware forwarding, so UL needs SDAP QFI marking,
|
||||
* and DL payload received on F1-U includes SDAP+PDCP headers. */
|
||||
drb->sdap_config.sDAP_Header_UL = true;
|
||||
drb->sdap_config.sDAP_Header_DL = true;
|
||||
drb->numCellGroups = 1;
|
||||
|
||||
return bearer_req;
|
||||
@@ -181,9 +197,10 @@ static up_params_t setup_cuup_ue_ng(sctp_assoc_t assoc_id, const e1ap_nssai_t *n
|
||||
in_addr_t addr_lo;
|
||||
inet_pton(AF_INET, ip, &addr_lo);
|
||||
|
||||
/* create the local tunnel (i.e., as if we were UPF) */
|
||||
/* create the local tunnel (i.e., as if we were UPF) with N3-U bearer mappings */
|
||||
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);
|
||||
/* Intentionally disable non-SDAP callback on NG-U: this test must fail if packets arrive without QFI. */
|
||||
teid_t teid_lo = newGtpuCreateTunnel(gtp_inst, ue_id, pdu_id, pdu_id, -1, null_addr, NULL, recv_ng);
|
||||
UP_TL_information_t tnl = {.teId = teid_lo};
|
||||
memcpy(&tnl.tlAddress, &addr_lo, 4);
|
||||
|
||||
@@ -263,7 +280,8 @@ static bool recv_f1(protocol_ctxt_t *ctxt,
|
||||
const uint32_t *sourceL2Id,
|
||||
const uint32_t *destinationL2Id)
|
||||
{
|
||||
int skip_bytes = 2;
|
||||
/* F1-U DL payload contains PDCP (2 bytes) + SDAP (1 byte) header */
|
||||
int skip_bytes = 3;
|
||||
uint32_t *payload = (uint32_t *) &buf[skip_bytes];
|
||||
DevAssert((size - skip_bytes) % 4 == 0);
|
||||
int ue_id = ctxt->rntiMaybeUEid;
|
||||
@@ -283,7 +301,7 @@ static void setup_cuup_ue_f1(sctp_assoc_t assoc_id, uint32_t ue_id, instance_t g
|
||||
/* 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);
|
||||
teid_t teid_lo = newGtpuCreateTunnel(gtp_inst, ue_id, drb_id, drb_id, rm.tl_info.teId, 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);
|
||||
@@ -408,18 +426,19 @@ static void *sender_thread(void *v)
|
||||
{
|
||||
struct thr_data *d = v;
|
||||
|
||||
// Overhead: PDCP header (2 bytes) for UL packets
|
||||
// SDAP headers are disabled, so only PDCP header is added
|
||||
size_t oh = d->is_ul ? 2 : 0;
|
||||
// UL F1-U payload overhead: PDCP header (2 bytes) + SDAP header (1 byte)
|
||||
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);
|
||||
/* SDAP UL data PDU: bits[5:0]=QFI (TEST_QOS_FLOW_ID=1), bit6=R=0, bit7=DC=0 (data PDU) */
|
||||
buf[2] = TEST_QOS_FLOW_ID;
|
||||
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;
|
||||
|
||||
const int dl_qfi = TEST_QOS_FLOW_ID;
|
||||
|
||||
//printf("sending %ld Mbps for %ld s => data %ld, %.3f pps\n", (float) d->throughput / 1000000.0, d->duration, to_send, pps_1ms);
|
||||
|
||||
@@ -435,7 +454,11 @@ static void *sender_thread(void *v)
|
||||
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);
|
||||
if (d->is_ul) { // UL direction: F1-U tunnel
|
||||
gtpv1uSendDirect(d->inst, d->ue_id, bearer, (uint8_t *)buf, packet_len, false, false);
|
||||
} else { // DL direction: NG-U tunnel with SDAP enabled
|
||||
gtpv1uSendDirectWithQFI(d->inst, d->ue_id, bearer, dl_qfi, (uint8_t *)buf, packet_len);
|
||||
}
|
||||
d->total_data += d->data_len;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user