Files
openairinterface5g/openair2/SDAP/nr_sdap/nr_sdap.c
Guido Casati 1edb0a09bb fix (sdap): select RX/TX SDAP headers on per-DRB RRC role
A DRB may have an uplink SDAP header but no downlink header (valid TS 38.331).
The old entity->enable_sdap flag was true if either direction had a header, so UE
downlink wrongly read byte 0 as QFI. IPv4 payloads start with 0x45 -> bogus QFI 5
and dropped traffic before the TUN (PR !158).

Header presence must follow TS 37.324 §5.2.1/§5.2.2 per DRB and direction, not a
single PDU-session-wide enable-sdap.

Also, store the default DRB as a `qfi2drb_t` mapping in the SDAP entity and use
it directly for fallback paths.

Changes:
- Add nr_sdap_drb_lookup() to return first qfi2drb_table row for a DRB
- nr_sdap_rx_entity(): parse QFI only when the receiving direction has a header,
  drop if the DRB has no table row
- nr_sdap_tx_entity(): UL/DL TX header bits from qfi2drb_map() (drb_id + entity_role),
  including default-DRB fallback
- nr_sdap_qfi2drb(): const qfi2drb_t *, remove SDAP_MAP_RULE_EMPTY
- nr_sdap_map_ctrl_pdu(): handle NULL from qfi2drb_map
- nr_sdap_qfi2drb_map_update(): sync entity_role on
  all QFIs for the DRB. At most one DRB when both headers absent.
- nr_sdap_entity_update_qos_flows(): keep per-DRB role from nr_sdap_drb_lookup(),
  skip update when DRB has no row
- Cleanup entity->enable_sdap
- nr_sdap.c: remove TUN-thread QFI drop, tx_entity owns unmappable-QFI handling
- replace default_drb with qfi2drb_t default_drb in SDAP entity and init
  default DRB id+role at SDAP entity creation

Refs:
- PR #158
- TS 37.324 §5.2.1, §5.2.2, §6.2.2.1
- TS 38.331 SDAP-Config (sdap-HeaderUL / sdap-HeaderDL)

Signed-off-by: Guido Casati <guido.casati@openairinterface.org>
2026-07-09 12:21:03 +02:00

347 lines
11 KiB
C

/*
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
#include "nr_sdap.h"
#include "assertions.h"
#include "utils.h"
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "nr_sdap_entity.h"
#include "common/utils/LOG/log.h"
#include "rlc.h"
#include "tuntap_if.h"
#include "system.h"
typedef struct sdap_tun_iface_s {
ue_id_t ue_id;
int pdusession_id;
int sock;
char *ifname;
int qfi;
struct sdap_tun_iface_s *next;
} sdap_tun_iface_t;
static sdap_tun_iface_t *sdap_tun_iface_list = NULL;
static void reblock_tun_socket(int fd)
{
int f;
f = fcntl(fd, F_GETFL, 0);
f &= ~(O_NONBLOCK);
if (fcntl(fd, F_SETFL, f) == -1) {
LOG_E(PDCP, "fcntl(F_SETFL) failed on fd %d: errno %d, %s\n", fd, errno, strerror(errno));
}
}
static void *sdap_tun_read_thread(void *arg);
static sdap_tun_iface_t *sdap_tun_iface_lookup(ue_id_t ue_id, int pdusession_id)
{
for (sdap_tun_iface_t *it = sdap_tun_iface_list; it != NULL; it = it->next) {
if (it->ue_id == ue_id && it->pdusession_id == pdusession_id)
return it;
}
return NULL;
}
void nr_sdap_tun_store_qfi(ue_id_t ue_id, int pdusession_id, uint8_t qfi)
{
DevAssert(qfi < SDAP_MAX_QFI);
sdap_tun_iface_t *iface = sdap_tun_iface_lookup(ue_id, pdusession_id);
if (iface == NULL)
return;
iface->qfi = qfi;
}
void nr_sdap_tun_attach(nr_sdap_entity_t *entity)
{
DevAssert(entity);
if (entity->pdusession_sock >= 0)
return;
sdap_tun_iface_t *iface = sdap_tun_iface_lookup(entity->ue_id, entity->pdusession_id);
if (iface == NULL)
return;
if (!entity->is_gnb && iface->qfi >= 0 && iface->qfi < SDAP_MAX_QFI) {
entity->qfi = iface->qfi;
LOG_I(SDAP, "UE %ld PDU session %d: cached QFI %d\n", entity->ue_id, entity->pdusession_id, entity->qfi);
}
/* For UE, reflect UP suspend/resume to the OS by toggling IFF_UP. */
if (!entity->is_gnb) {
LOG_I(SDAP, "UE %ld PDU session %d: bringing TUN %s up\n", entity->ue_id, entity->pdusession_id, iface->ifname);
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd >= 0) {
tuntap_set_up(iface->ifname, fd);
close(fd);
}
}
int d = dup(iface->sock);
if (d < 0)
LOG_W(SDAP, "dup(tun sock) failed: errno %d %s\n", errno, strerror(errno));
else
reblock_tun_socket(d);
entity->pdusession_sock = d;
if (d < 0)
return;
entity->stop_thread = false;
char thread_name[64];
if (entity->is_gnb) {
snprintf(thread_name, sizeof(thread_name), "gnb_tun_read_thread");
} else {
snprintf(thread_name,
sizeof(thread_name),
"ue_tun_read_%ld_p%d",
entity->ue_id,
entity->pdusession_id);
}
threadCreate(&entity->pdusession_thread, sdap_tun_read_thread, entity, thread_name, -1, OAI_PRIORITY_RT_LOW);
}
static sdap_tun_iface_t *sdap_tun_iface_register(ue_id_t ue_id, int pdusession_id, int sock, const char *ifname)
{
DevAssert(sdap_tun_iface_lookup(ue_id, pdusession_id) == NULL);
sdap_tun_iface_t *iface = calloc_or_fail(1, sizeof(*iface));
iface->ue_id = ue_id;
iface->pdusession_id = pdusession_id;
iface->sock = sock;
iface->qfi = -1;
iface->ifname = strdup(ifname);
AssertFatal(iface->ifname != NULL, "strdup(ifname) failed\n");
nr_sdap_entity_t *entity = nr_sdap_get_entity(ue_id, pdusession_id);
if (entity != NULL && !entity->is_gnb && entity->qfi >= 0 && entity->qfi < SDAP_MAX_QFI)
iface->qfi = entity->qfi;
iface->next = sdap_tun_iface_list;
sdap_tun_iface_list = iface;
return iface;
}
bool sdap_data_req(protocol_ctxt_t *ctxt_p,
const ue_id_t ue_id,
const srb_flag_t srb_flag,
const mui_t mui,
const confirm_t confirm,
const sdu_size_t sdu_buffer_size,
unsigned char *const sdu_buffer,
const pdcp_transmission_mode_t pt_mode,
const uint32_t *sourceL2Id,
const uint32_t *destinationL2Id,
const uint8_t qfi,
const bool rqi,
const int pdusession_id) {
nr_sdap_entity_t *sdap_entity;
sdap_entity = nr_sdap_get_entity(ue_id, pdusession_id);
if(sdap_entity == NULL) {
LOG_E(SDAP, "%s:%d:%s: Entity not found with ue: 0x%"PRIx64" and pdusession id: %d\n", __FILE__, __LINE__, __FUNCTION__, ue_id, pdusession_id);
return 0;
}
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)
{
nr_sdap_entity_t *sdap_entity;
sdap_entity = nr_sdap_get_entity(ue_id, pdusession_id);
if (sdap_entity == NULL) {
LOG_E(SDAP, "Entity not found for ue rnti/ue_id: %lx and pdusession id: %d\n", ue_id, pdusession_id);
return;
}
sdap_entity->rx_entity(sdap_entity,
pdcp_entity,
is_gnb,
pdusession_id,
ue_id,
buf,
size);
}
static void *sdap_tun_read_thread(void *arg)
{
DevAssert(arg != NULL);
nr_sdap_entity_t *entity = arg;
char rx_buf[NL_MAX_PAYLOAD];
int len;
DevAssert(entity->pdusession_sock >= 0);
reblock_tun_socket(entity->pdusession_sock);
while (!entity->stop_thread) {
len = read(entity->pdusession_sock, &rx_buf, NL_MAX_PAYLOAD);
if (len == -1) {
if (errno == EINTR)
continue; // interrupted system call
if (errno == EBADF || errno == EINVAL) {
LOG_I(SDAP, "Socket closed, exiting TUN read thread for UE %ld, PDU session %d\n", entity->ue_id, entity->pdusession_id);
break;
}
LOG_E(PDCP, "read() failed: errno %d (%s)\n", errno, strerror(errno));
break;
}
if (len == 0) {
LOG_W(SDAP, "TUN socket returned EOF - exiting thread\n");
break;
}
LOG_D(SDAP, "read data of size %d\n", len);
protocol_ctxt_t ctxt = {.enb_flag = entity->is_gnb, .rntiMaybeUEid = entity->ue_id};
bool dc = entity->is_gnb ? false : SDAP_HDR_UL_DATA_PDU;
entity->tx_entity(entity,
&ctxt,
SRB_FLAG_NO,
RLC_MUI_UNDEFINED,
RLC_SDU_CONFIRM_NO,
len,
(unsigned char *)rx_buf,
PDCP_TRANSMISSION_MODE_DATA,
NULL,
NULL,
entity->qfi,
dc);
}
return NULL;
}
void nr_sdap_tun_detach(nr_sdap_entity_t *entity)
{
DevAssert(entity != NULL);
sdap_tun_iface_t *iface = NULL;
if (!entity->is_gnb) {
iface = sdap_tun_iface_lookup(entity->ue_id, entity->pdusession_id);
if (iface != NULL && entity->qfi >= 0 && entity->qfi < SDAP_MAX_QFI)
iface->qfi = entity->qfi; // store the QFI for the next attach
}
if (entity->pdusession_sock < 0)
return;
entity->stop_thread = true;
close(entity->pdusession_sock);
entity->pdusession_sock = -1;
/* For UE, bring interface down so the OS reflects UP suspension. */
if (!entity->is_gnb && iface != NULL) {
LOG_I(SDAP, "UE %ld PDU session %d: bringing TUN %s down\n", entity->ue_id, entity->pdusession_id, iface->ifname);
tuntap_destroy(iface->ifname);
}
int cancel_ret = pthread_cancel(entity->pdusession_thread);
AssertFatal(cancel_ret == 0, "pthread_cancel() failed: %d (%s)\n", cancel_ret, strerror(cancel_ret));
int ret = pthread_join(entity->pdusession_thread, NULL);
AssertFatal(ret == 0, "pthread_join() failed: %d (%s)\n", ret, strerror(ret));
}
void nr_sdap_tun_destroy(ue_id_t ue_id, int pdusession_id)
{
sdap_tun_iface_t *iface = NULL;
for (sdap_tun_iface_t **pp = &sdap_tun_iface_list; *pp != NULL; pp = &(*pp)->next) {
if ((*pp)->ue_id == ue_id && (*pp)->pdusession_id == pdusession_id) {
iface = *pp;
*pp = iface->next;
iface->next = NULL;
break;
}
}
if (iface == NULL) {
LOG_D(SDAP, "nr_sdap_tun_destroy: no iface (ue=%ld, pdu=%d)\n", ue_id, pdusession_id);
return;
}
close(iface->sock);
tuntap_destroy(iface->ifname);
LOG_I(SDAP, "Destroyed TUN dataplane for UE %ld PDU session %d (%s)\n", iface->ue_id, iface->pdusession_id, iface->ifname);
free(iface->ifname);
free(iface);
}
void start_sdap_tun_gnb_first_ue_default_pdu_session(ue_id_t ue_id, int pdu_session_id)
{
nr_sdap_entity_t *entity = nr_sdap_get_entity(ue_id, pdu_session_id);
DevAssert(entity != NULL);
DevAssert(entity->is_gnb);
char *ifprefix = get_softmodem_params()->nsa ? "oaitun_gnb" : "oaitun_enb";
char ifname[IFNAMSIZ];
tun_generate_ifname(ifname, ifprefix, ue_id - 1);
const int sock = tuntap_alloc(IFF_TUN, ifname);
tun_config(ifname, "10.0.1.1", NULL);
sdap_tun_iface_register(entity->ue_id, entity->pdusession_id, sock, ifname);
nr_sdap_tun_attach(entity);
}
static void start_sdap_tun_ue(ue_id_t ue_id, int pdu_session_id, int sock, const char *ifname)
{
nr_sdap_entity_t *entity = nr_sdap_get_entity(ue_id, pdu_session_id);
DevAssert(entity != NULL);
DevAssert(!entity->is_gnb);
// First PDU session setup: register UE TUN and attach the reader thread
sdap_tun_iface_register(ue_id, pdu_session_id, sock, ifname);
nr_sdap_tun_attach(entity);
}
void create_ue_ip_if(const char *ipv4, const char *ipv6, int ue_id, int pdu_session_id, bool is_default)
{
char ifname[IFNAMSIZ];
tuntap_generate_ue_ifname(ifname, IFF_TUN, ue_id, is_default ? -1 : pdu_session_id);
if (sdap_tun_iface_lookup(ue_id, pdu_session_id) == NULL) {
const int sock = tuntap_alloc(IFF_TUN, ifname);
start_sdap_tun_ue(ue_id, pdu_session_id, sock, ifname);
} else {
nr_sdap_entity_t *entity = nr_sdap_get_entity(ue_id, pdu_session_id);
if (entity != NULL)
nr_sdap_tun_attach(entity);
}
tun_config(ifname, ipv4, ipv6);
if (ipv4) {
setup_ue_ipv4_route(ifname, ue_id, ipv4);
}
}
void create_ue_eth_if(int ue_id, int pdu_session_id, bool is_default)
{
char ifname[IFNAMSIZ];
tuntap_generate_ue_ifname(ifname, IFF_TAP, ue_id, is_default ? -1 : pdu_session_id);
if (sdap_tun_iface_lookup(ue_id, pdu_session_id) == NULL) {
const int sock = tuntap_alloc(IFF_TAP, ifname);
start_sdap_tun_ue(ue_id, pdu_session_id, sock, ifname);
} else {
nr_sdap_entity_t *entity = nr_sdap_get_entity(ue_id, pdu_session_id);
if (entity != NULL)
nr_sdap_tun_attach(entity);
}
tap_config(ifname);
}