Merge remote-tracking branch 'GuidoCasati/rrc-fixes' into integration_2026_w28

RRC fixes (#225)

- Hardens NR RRC re-establishment handling on the gNB and UE:
  re-establishment requests with invalid PCI/C-RNTI or incomplete UE
  context are rejected or fall back to RRCSetup per TS 38.33.
- Fixes an invalid post-release connected state after PDU session
  teardown
- Stops gNB process aborts when a UE sends spare RRC establishment-cause
  values.
- Fix RRCSetup fallback: the UE MAC layer is reset before bearer
  teardown on RRCSetup fallback to avoid scheduler use of released RLC
  entities.

Closes: #127
Closes: #128
Closes: #148
Reviewed-by: Robert Schmidt <robert.schmidt@openairinterface.org>
Reviewed-by: Francesco Mani <email@francescomani.it>
This commit is contained in:
Robert Schmidt
2026-07-06 18:27:13 +02:00
6 changed files with 107 additions and 28 deletions

View File

@@ -57,6 +57,8 @@
/** Maximum number of Paging Occasions per Paging Frame (TS 38.331 PCCH-Config) */
#define NR_PCCH_MAX_PO 4
#define NR_PHYS_CELL_ID_MAX 1007 /* Maximum Physical Cell ID (0..1007) */
#define NB_RB_MBMS_MAX (29 * 16) /* 29 = LTE_maxSessionPerPMCH + 16 = LTE_maxServiceCount from LTE_asn_constant.h */
#define NB_RAB_MAX 11 /* from LTE_maxDRB in LTE_asn_constant.h */

View File

@@ -1901,6 +1901,12 @@ void nr_rrc_mac_config_req_reset(module_id_t module_id, NR_UE_MAC_reset_cause_t
mac->state = UE_BARRED;
break;
case RRC_SETUP_REESTAB_RESUME:
for (int i = mac->lc_ordered_list.count; i > 0; i--) {
nr_lcordered_info_t *lc = mac->lc_ordered_list.array[i - 1];
if (lc->rb.type == NR_LCID_SRB && lc->rb.choice.srb_id == 0)
continue;
asn_sequence_del(&mac->lc_ordered_list, i - 1, 1);
}
release_mac_configuration(mac, cause);
nr_ue_mac_default_configs(mac);
break;

View File

@@ -96,6 +96,13 @@ mui_t rrc_gNB_mui = 0;
/* Per-transaction max_delays counter to limit retry attempts */
#define MAX_DELAYS 100
/* C-RNTI range (0001-FFF2) (TS 38.321 Table 7.1-1, Rel-16+) */
#define NR_C_RNTI_MIN 0x0001
#define NR_C_RNTI_MAX 0xfff2
/* 5.3.3.3 TS 38.331: Random UE identity mask for 39-bit values */
#define NR_RRC_RANDOM_VALUE_39_BIT_MASK (0x7fffffffffULL)
/** @brief clone and re-enqueue an NGAP message after delaying
* delays the ongoing transaction (in msg_p) by setting a timer to wait
* 10ms; upon expiry, delivers to RRC, which sends the message to itself */
@@ -1084,9 +1091,8 @@ static DRB_nGRAN_to_mod_t get_e1_drb_mod_reestablishment(const drb_t *drb, const
return drb_e1;
}
/**
* @brief Notify E1 re-establishment to CU-UP
*/
/** @brief Re-establish DRB PDCP on CU-UP (TS 38.331 clause 5.3.5.6.5, TS 38.463 bearer mod).
* Sends pDCP_Reestablishment and updated KUP keys after KgNB derivation. */
static void cuup_notify_reestablishment(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue_p)
{
// Quit if no CU-UP is associated
@@ -1094,6 +1100,10 @@ static void cuup_notify_reestablishment(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue_p)
return;
}
/* TS 38.331 §5.3.5.6.5: no DRB/PDU session (e.g. after release) means nothing to do. */
if (seq_arr_size(&ue_p->drbs) == 0)
return;
e1ap_bearer_mod_req_t req = {
.gNB_cu_cp_ue_id = ue_p->rrc_ue_id,
.gNB_cu_up_ue_id = ue_p->rrc_ue_id,
@@ -1431,11 +1441,20 @@ static const nr_rrc_cell_container_t *get_previous_cell_by_pci_in_du(gNB_RRC_INS
return rrc_get_cell_by_pci_for_du(&du->cells, pci);
}
/** @brief Process RRCReestablishmentRequest on CCCH (TS 38.331 clause 5.3.7.4).
* On valid UE context, update RNTI and PCell and trigger RRCReestablishment, otherwise
* release any old context and send RRCSetup.
* @note Context lookup uses c-RNTI only. Out of range PhysCellId or C-RNTI are ignored. */
static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *rrc,
sctp_assoc_t assoc_id,
const NR_RRCReestablishmentRequest_IEs_t *req,
const f1ap_initial_ul_rrc_message_t *msg)
{
DevAssert(req);
DevAssert(msg);
DevAssert(rrc);
RETURN_IF_INVALID_ASSOC_ID(assoc_id);
uint64_t random_value = 0;
const char *scause = get_reestab_cause(req->reestablishmentCause);
const long physCellId = req->ue_Identity.physCellId;
@@ -1450,6 +1469,18 @@ static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *rrc,
physCellId,
scause);
/* Validate PCI range per TS 38.331: PhysCellId (0..1007) */
if (physCellId < 0 || physCellId > NR_PHYS_CELL_ID_MAX) {
LOG_E(NR_RRC, "Invalid physCellId %ld (valid range: 0-%d), rejecting reestablishment request\n", physCellId, NR_PHYS_CELL_ID_MAX);
return;
}
/* TS 38.321 Table 7.1-1: out-of-range C-RNTI, ignore request */
if (old_rnti < NR_C_RNTI_MIN || old_rnti > NR_C_RNTI_MAX) {
LOG_E(NR_RRC, "C-RNTI %04x out of range (%#04x-%#04x): rejecting RRCReestablishmentRequest\n", old_rnti, NR_C_RNTI_MIN, NR_C_RNTI_MAX);
return;
}
const nr_rrc_du_container_t *du = get_du_by_assoc_id(rrc, assoc_id);
if (du == NULL) {
LOG_E(RRC, "received CCCH message, but no corresponding DU found\n");
@@ -1466,12 +1497,6 @@ static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *rrc,
return;
}
// Validate C-RNTI range (3GPP TS 38.321 version 15.13.0 Section 7.1 Table 7.1-1)
if (old_rnti < 0x1 || old_rnti > 0xffef) {
LOG_E(NR_RRC, "NR_RRCReestablishmentRequest c_RNTI %04x range error, fallback to RRC setup\n", old_rnti);
goto fallback_rrc_setup;
}
if (current_cell->mtc == NULL) {
// some UEs don't send MeasurementTimingConfiguration, so we don't know the
// SSB ARFCN and can't do reestablishment. handle it gracefully by doing
@@ -1487,6 +1512,8 @@ static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *rrc,
return;
}
/* TS 38.331 §5.3.7.1: retrieve UE context (C-RNTI + physCellId): if it cannot be
* retrieved, respond with RRCSetup (Fig. 5.3.7.1-2). */
ue_context_p = rrc_gNB_get_ue_context_by_rnti(rrc, assoc_id, old_rnti);
if (ue_context_p == NULL) {
// Fallback 1: Try to find UE by RNTI only (re-establishment on different DU scenario)
@@ -1509,6 +1536,15 @@ static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *rrc,
goto fallback_rrc_setup;
}
/* TS 38.331 5.3.7.1: requires a retrieved valid UE context. Context without
* SRB2 or any DRB is incomplete for re-establishment (UE initiation needs both,
* treat as not verified). */
if (!UE->Srb[SRB2].Active || seq_arr_size(&UE->drbs) == 0) {
LOG_E(NR_RRC, "UE context not valid for re-establishment (no SRB2/DRB), fallback to RRC setup\n");
ngap_cause = NGAP_CAUSE_RADIO_NETWORK_RELEASE_DUE_TO_NGRAN_GENERATED_REASON;
goto fallback_rrc_setup;
}
f1_ue_data_t ue_data = cu_get_f1_ue_data(UE->rrc_ue_id);
nr_ho_source_cu_t *source_ctx = UE->ho_context ? UE->ho_context->source : NULL;
@@ -1617,7 +1653,7 @@ static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *rrc,
fallback_rrc_setup:
fill_random(&random_value, sizeof(random_value));
random_value = random_value & 0x7fffffffff; /* random value is 39 bits */
random_value = random_value & NR_RRC_RANDOM_VALUE_39_BIT_MASK;
ngap_cause_t cause = {.type = NGAP_CAUSE_RADIO_NETWORK, .value = ngap_cause};
/* request release of the "old" UE in case it exists */

View File

@@ -222,9 +222,8 @@ void rrc_gNB_send_NGAP_NAS_FIRST_REQ(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, NR_RRC
req->gNB_ue_ngap_id = UE->rrc_ue_id;
// RRC Establishment Cause
/* Assume that cause is coded in the same way in RRC and NGap, just check that the value is in NGap range */
AssertFatal(UE->establishment_cause < NGAP_RRC_CAUSE_LAST, "Establishment cause invalid (%jd/%d)!", UE->establishment_cause, NGAP_RRC_CAUSE_LAST);
req->establishment_cause = UE->establishment_cause;
req->establishment_cause =
UE->establishment_cause <= NGAP_RRC_CAUSE_MCS_PRIORITY_ACCESS ? UE->establishment_cause : NGAP_RRC_CAUSE_NOTAVAILABLE;
// NAS-PDU
req->nas_pdu = create_byte_array(rrcSetupComplete->dedicatedNAS_Message.size, rrcSetupComplete->dedicatedNAS_Message.buf);
@@ -1869,6 +1868,33 @@ void rrc_gNB_send_NGAP_HANDOVER_CANCEL(int module_id, gNB_RRC_UE_t *UE, ngap_cau
itti_send_msg_to_task(TASK_NGAP, module_id, msg_p);
}
/**
* @brief Enforce TS 38.331 §5.3.1.1 after the last DRB of a PDU session release.
* A UE is not allowed to have a configuration with SRB2 but no DRB, and vice versa.
* @note Per TS 23.502 section 4.3.4.2 step 5, NG-RAN releases PDU-session AN resources
* via RRCReconfiguration. If that leaves no DRB with SRB2 still up, request UE context
* release (TS 38.413 section 8.3.2.2). AMF UE Context Release Command triggers RRCRelease
* on the existing CU-CP path. */
static void rrc_gNB_cleanup_srb2_only_connected(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE)
{
DevAssert(seq_arr_size(&UE->drbs) == 0);
DevAssert(UE->Srb[SRB2].Active);
rrc_gNB_ue_context_t *ue_context_p = rrc_gNB_get_ue_context(rrc, UE->rrc_ue_id);
if (!ue_context_p) {
LOG_W(NR_RRC, "UE %u: no RRC context for connection release after last DRB\n", UE->rrc_ue_id);
return;
}
LOG_I(NR_RRC, "UE %u: last DRB released with SRB2 still active: requesting RRC connection release\n", UE->rrc_ue_id);
ngap_cause_t cause = {
.type = NGAP_CAUSE_RADIO_NETWORK,
.value = NGAP_CAUSE_RADIO_NETWORK_RELEASE_DUE_TO_NGRAN_GENERATED_REASON,
};
rrc_gNB_send_NGAP_UE_CONTEXT_RELEASE_REQ(rrc->module_id, ue_context_p, cause);
}
void rrc_gNB_send_NGAP_PDUSESSION_RELEASE_RESPONSE(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, uint8_t xid)
{
MessageDef *msg_p;
@@ -1893,6 +1919,10 @@ void rrc_gNB_send_NGAP_PDUSESSION_RELEASE_RESPONSE(gNB_RRC_INST *rrc, gNB_RRC_UE
LOG_I(NR_RRC, "NGAP PDUSESSION RELEASE RESPONSE: rrc_ue_id %u release_pdu_sessions %d\n", resp->gNB_ue_ngap_id, resp->nb_of_pdusessions_released);
itti_send_msg_to_task (TASK_NGAP, rrc->module_id, msg_p);
/* TS 38.331 §5.3.1.1: cannot keep SRB2 in RRC_CONNECTED after all DRBs are gone. */
if (seq_arr_size(&UE->drbs) == 0 && UE->Srb[SRB2].Active)
rrc_gNB_cleanup_srb2_only_connected(rrc, UE);
}
/** @brief Process NG PDU Session Resource Release command (8.2.2 of 3GPP TS 38.413)

View File

@@ -2147,7 +2147,7 @@ static void rrc_ue_generate_RRCSetupComplete(NR_UE_RRC_INST_t *rrc, const uint8_
static void nr_rrc_rrcsetup_fallback(NR_UE_RRC_INST_t *rrc)
{
LOG_W(NR_RRC,
"[UE %ld] Recived RRCSetup in response to %s request\n",
"[UE %ld] Received RRCSetup in response to %s request\n",
rrc->ue_id, rrc->ra_trigger == RRC_CONNECTION_REESTABLISHMENT ? "RRCReestablishment" : "RRCResume");
// discard any stored UE Inactive AS context and suspendConfig
@@ -2159,8 +2159,15 @@ static void nr_rrc_rrcsetup_fallback(NR_UE_RRC_INST_t *rrc)
memset(rrc->kgnb, 0, sizeof(rrc->kgnb));
rrc->as_security_activated = false;
// release the RRC configuration except for the default L1 parameter values,
// default MAC Cell Group configuration and CCCH configuration
nr_mac_rrc_message_t rrc_msg = {0};
rrc_msg.payload_type = NR_MAC_RRC_CONFIG_RESET;
rrc_msg.payload.config_reset.cause = RRC_SETUP_REESTAB_RESUME;
nr_rrc_send_msg_to_mac(rrc, &rrc_msg);
// release radio resources for all established RBs except SRB0,
// including release of the RLC entities, of the associated PDCP entities and of SDAP
// including release of the associated PDCP entities and of SDAP
for (int i = 1; i <= MAX_DRBS_PER_UE; i++) {
if (get_DRB_status(rrc, i) != RB_NOT_PRESENT) {
set_DRB_status(rrc, i, RB_NOT_PRESENT);
@@ -2178,15 +2185,6 @@ static void nr_rrc_rrcsetup_fallback(NR_UE_RRC_INST_t *rrc)
}
nr_sdap_delete_ue_entities(rrc->ue_id);
// release the RRC configuration except for the default L1 parameter values,
// default MAC Cell Group configuration and CCCH configuration
// TODO to be completed
NR_UE_MAC_reset_cause_t cause = RRC_SETUP_REESTAB_RESUME;
nr_mac_rrc_message_t rrc_msg = {0};
rrc_msg.payload_type = NR_MAC_RRC_CONFIG_RESET;
rrc_msg.payload.config_reset.cause = cause;
nr_rrc_send_msg_to_mac(rrc, &rrc_msg);
// indicate to upper layers fallback of the RRC connection
// TODO

View File

@@ -126,6 +126,16 @@ static ngap_gNB_amf_data_t *select_amf(ngap_gNB_instance_t *instance_p, const ng
return amf;
}
/** @brief Map UE EstablishmentCause (TS 38.331) to NGAP RRCEstablishmentCause (clause 9.3.1.111).
* Values 0-9 pass through, otherwise return notAvailable. */
static NGAP_RRCEstablishmentCause_t rrc2ngap_establishment_cause(ngap_rrc_establishment_cause_t cause)
{
if (cause <= NGAP_RRC_CAUSE_MCS_PRIORITY_ACCESS)
return cause;
/* clause 9.3.1.111: notAvailable when the UE cause does not map to any other value */
return NGAP_RRCEstablishmentCause_notAvailable;
}
/** @brief NAS Transport Messages: Initial UE Message
* forward the first received (layer 3) uplink NAS message
* from the radio interface to the AMF over N2
@@ -201,16 +211,13 @@ int ngap_gNB_handle_nas_first_req(instance_t instance, ngap_nas_first_req_t *UEf
MCC_MNC_TO_PLMNID(plmn->mcc, plmn->mnc, plmn->mnc_digit_length, &userinfo_nr_p->tAI.pLMNIdentity);
}
/* Set the establishment cause according to those provided by RRC */
DevCheck(UEfirstReq->establishment_cause < NGAP_RRC_CAUSE_LAST, UEfirstReq->establishment_cause, NGAP_RRC_CAUSE_LAST, 0);
// RRC Establishment Cause (M)
{
asn1cSequenceAdd(out->protocolIEs.list, NGAP_InitialUEMessage_IEs_t, ie);
ie->id = NGAP_ProtocolIE_ID_id_RRCEstablishmentCause;
ie->criticality = NGAP_Criticality_ignore;
ie->value.present = NGAP_InitialUEMessage_IEs__value_PR_RRCEstablishmentCause;
ie->value.choice.RRCEstablishmentCause = UEfirstReq->establishment_cause;
ie->value.choice.RRCEstablishmentCause = rrc2ngap_establishment_cause(UEfirstReq->establishment_cause);
}
// 5G-S-TMSI (O)