Merge remote-tracking branch 'origin/CI-ho-attenuator-N2' into integration_2025_w43 (!3708)
Add monolithic N2 handover test pipeline with B210 and Quectel The goal of this MR is to provide an automated end-to-end N2 HO validation with real RF (B210) and Quectel UE. During the testing phase a post-HO assertion failure due to uninitialized selected_plmn_identity was found: this was also fixed. CI: Add monolithic N2 handover test pipeline (B210 + Quectel) - New XML pipeline for N2 HO testing - Docker Compose for two monolithic gNBs (gNB1/gNB2) - Handover sequence: gNB1 → gNB2 → gNB1 with 5s controlled attenuation (RC4DAT). - Channel mapping: ch1-2 gNB1, ch3-4 gNB2 - Telnet server enabled for CI commands - Clean-up/reset of attenuator state included NGAP - fix PLMN/cell location for Initial UE and Uplink NAS - Prevents assertion in ngap_gNB_nas_uplink() due to uninitialized selected_plmn_identity - Initial UE Message: pass UE-selected PLMN and NR cell ID from RRC to NGAP - Uplink NAS Transport: use PLMN, TAC, and cell ID from the message - Build NR CGI as gNB ID + cell ID - Store UE-selected PLMN only in RRC UE context (for later NAS); stop storing PLMN in NGAP UE context. - PLMN validation when processing handover requests from the source gNB: when PLMN validation fails, the target gNB now properly sends NGAP_HANDOVER_FAILURE - Store serving PLMN for later use in UE context
This commit is contained in:
@@ -421,6 +421,8 @@ typedef struct ngap_nas_first_req_s {
|
||||
/* PLMN: Selected PLMN Identity (optional)
|
||||
* User Location Information (mandatory) */
|
||||
plmn_id_t plmn;
|
||||
// NR Cell ID for NR CGI (mandatory)
|
||||
uint32_t nr_cell_id;
|
||||
// RRC Establishment Cause (mandatory)
|
||||
ngap_rrc_establishment_cause_t establishment_cause;
|
||||
// NAS-PDU (mandatory)
|
||||
@@ -434,6 +436,10 @@ typedef struct ngap_uplink_nas_s {
|
||||
uint32_t gNB_ue_ngap_id;
|
||||
/* NAS pdu */
|
||||
byte_array_t nas_pdu;
|
||||
/* UserLocationInformation (mandatory) */
|
||||
plmn_id_t plmn; // CGI and TAI
|
||||
uint32_t nr_cell_id; // CGI
|
||||
uint32_t tac; // TAI
|
||||
} ngap_uplink_nas_t;
|
||||
|
||||
typedef struct target_cell_id_s {
|
||||
|
||||
@@ -38,13 +38,11 @@ ue_id_e2sm_t fill_e2sm_gnb_ue_id_data(const gNB_RRC_UE_t *rrc_ue_context, __attr
|
||||
ue_id.gnb.amf_ue_ngap_id = auni < (1LL << 40) ? auni : 0;
|
||||
|
||||
// Mandatory
|
||||
//GUAMI 6.2.3.17
|
||||
ue_id.gnb.guami.plmn_id = (e2sm_plmn_t) {
|
||||
.mcc = rrc_ue_context->ue_guami.mcc,
|
||||
.mnc = rrc_ue_context->ue_guami.mnc,
|
||||
.mnc_digit_len = rrc_ue_context->ue_guami.mnc_len
|
||||
};
|
||||
|
||||
//GUAMI 6.2.3.17
|
||||
ue_id.gnb.guami.plmn_id = (e2sm_plmn_t){.mcc = rrc_ue_context->ue_guami.plmn.mcc,
|
||||
.mnc = rrc_ue_context->ue_guami.plmn.mnc,
|
||||
.mnc_digit_len = rrc_ue_context->ue_guami.plmn.mnc_digit_length};
|
||||
|
||||
ue_id.gnb.guami.amf_region_id = rrc_ue_context->ue_guami.amf_region_id;
|
||||
ue_id.gnb.guami.amf_set_id = rrc_ue_context->ue_guami.amf_set_id;
|
||||
ue_id.gnb.guami.amf_ptr = rrc_ue_context->ue_guami.amf_pointer;
|
||||
|
||||
@@ -209,6 +209,8 @@ typedef struct gNB_RRC_UE_s {
|
||||
uint64_t amf_ue_ngap_id;
|
||||
// Globally Unique AMF Identifier
|
||||
nr_guami_t ue_guami;
|
||||
// Serving PLMN of the UE
|
||||
plmn_id_t serving_plmn;
|
||||
|
||||
ngap_security_capabilities_t security_capabilities;
|
||||
//NSA block
|
||||
|
||||
@@ -110,6 +110,24 @@ static const uint16_t NGAP_INTEGRITY_NIA3_MASK = 0x2000;
|
||||
|
||||
static void set_UE_security_algos(const gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, const ngap_security_capabilities_t *cap);
|
||||
|
||||
/** @brief Validates PLMN against allowed PLMN list and returns pointer to matching PLMN
|
||||
* @param rrc RRC instance
|
||||
* @param plmn PLMN to validate
|
||||
* @return pointer to matching PLMN in configuration, or NULL if not found */
|
||||
static const plmn_id_t *get_serving_plmn(gNB_RRC_INST *rrc, const plmn_id_t *plmn)
|
||||
{
|
||||
// Find matching PLMN in configuration
|
||||
for (int idx = 0; idx < rrc->configuration.num_plmn; idx++) {
|
||||
const plmn_id_t *allowed = &rrc->configuration.plmn[idx];
|
||||
if (allowed->mcc == plmn->mcc && allowed->mnc == plmn->mnc && allowed->mnc_digit_length == plmn->mnc_digit_length) {
|
||||
LOG_D(NR_RRC, "PLMN (MCC:%d, MNC:%*d) matched with allowed PLMN[%d]\n", plmn->mcc, plmn->mnc_digit_length, plmn->mnc, idx);
|
||||
return allowed;
|
||||
}
|
||||
}
|
||||
LOG_W(NR_RRC, "PLMN (MCC:%d, MNC:%*d) not found in allowed PLMN list\n", plmn->mcc, plmn->mnc_digit_length, plmn->mnc);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*!
|
||||
*\brief save security key.
|
||||
*\param UE UE context.
|
||||
@@ -165,9 +183,7 @@ static nr_guami_t get_guami(const uint32_t amf_Id, const plmn_id_t plmn)
|
||||
guami.amf_region_id = (amf_Id >> 16) & 0xff;
|
||||
guami.amf_set_id = (amf_Id >> 6) & 0x3ff;
|
||||
guami.amf_pointer = amf_Id & 0x3f;
|
||||
guami.mcc = plmn.mcc;
|
||||
guami.mnc = plmn.mnc;
|
||||
guami.mnc_len = plmn.mnc_digit_length;
|
||||
guami.plmn = plmn;
|
||||
return guami;
|
||||
}
|
||||
|
||||
@@ -223,17 +239,22 @@ void rrc_gNB_send_NGAP_NAS_FIRST_REQ(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, NR_RRC
|
||||
req->nas_pdu = create_byte_array(rrcSetupComplete->dedicatedNAS_Message.size, rrcSetupComplete->dedicatedNAS_Message.buf);
|
||||
|
||||
/* Selected PLMN Identity (Optional)
|
||||
* selectedPLMN-Identity in RRCSetupComplete: Index of the PLMN selected by the UE from the plmn-IdentityInfoList (SIB1)
|
||||
* Selected PLMN Identity in INITIAL UE MESSAGE: Indicates the selected PLMN id for the non-3GPP access.*/
|
||||
if (rrcSetupComplete->selectedPLMN_Identity > rrc->configuration.num_plmn) {
|
||||
* selectedPLMN-Identity in RRCSetupComplete: Index of the PLMN selected by the UE from the plmn-IdentityInfoList (SIB1) */
|
||||
int idx = rrcSetupComplete->selectedPLMN_Identity - 1; // Convert 1-based PLMN Identity IE to 0-based index
|
||||
if (idx < 0 || idx >= rrc->configuration.num_plmn) {
|
||||
LOG_E(NGAP,
|
||||
"Failed to send Initial UE Message: selected PLMN (%ld) identity is out of bounds (%d)\n",
|
||||
rrcSetupComplete->selectedPLMN_Identity,
|
||||
"Failed to send Initial UE Message: selected PLMN index (%d) is out of bounds [0..%d)\n",
|
||||
idx,
|
||||
rrc->configuration.num_plmn);
|
||||
return;
|
||||
}
|
||||
int selected_plmn_identity = rrcSetupComplete->selectedPLMN_Identity - 1; // Convert 1-based PLMN Identity IE to 0-based index
|
||||
req->plmn = rrc->configuration.plmn[selected_plmn_identity]; // Select from the stored list
|
||||
req->plmn = rrc->configuration.plmn[idx]; // Select from the stored list
|
||||
/* UE is connected to only one PLMN at a time: store this as the serving PLMN */
|
||||
UE->serving_plmn = req->plmn;
|
||||
|
||||
// Cell ID (NR CGI)
|
||||
req->nr_cell_id = UE->nr_cellid;
|
||||
// PLMN (NR CGI and TAI)
|
||||
plmn_id_t *p = &req->plmn;
|
||||
LOG_I(NGAP, "Selected PLMN in the NG Initial UE Message: MCC=%03d MNC=%0*d\n", p->mcc, p->mnc_digit_length, p->mnc);
|
||||
|
||||
@@ -721,6 +742,10 @@ void rrc_gNB_send_NGAP_UPLINK_NAS(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, const NR_
|
||||
NGAP_UPLINK_NAS(msg_p).gNB_ue_ngap_id = UE->rrc_ue_id;
|
||||
NGAP_UPLINK_NAS(msg_p).nas_pdu.len = nas->size;
|
||||
NGAP_UPLINK_NAS(msg_p).nas_pdu.buf = buf;
|
||||
/* Fill PLMN and location info: use serving PLMN of the UE */
|
||||
NGAP_UPLINK_NAS(msg_p).plmn = UE->serving_plmn;
|
||||
NGAP_UPLINK_NAS(msg_p).nr_cell_id = rrc->nr_cellid;
|
||||
NGAP_UPLINK_NAS(msg_p).tac = rrc->configuration.tac;
|
||||
itti_send_msg_to_task(TASK_NGAP, rrc->module_id, msg_p);
|
||||
}
|
||||
|
||||
@@ -1154,6 +1179,20 @@ int rrc_gNB_process_Handover_Request(gNB_RRC_INST *rrc, instance_t instance, nga
|
||||
rrc_gNB_send_NGAP_HANDOVER_FAILURE(rrc, &fail);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Validate PLMN from GUAMI against allowed PLMN list
|
||||
const plmn_id_t *serving_plmn = get_serving_plmn(rrc, &msg->guami.plmn);
|
||||
if (!serving_plmn) {
|
||||
LOG_E(NR_RRC, "PLMN from GUAMI not supported - rejecting handover\n");
|
||||
ngap_handover_failure_t fail = {
|
||||
.amf_ue_ngap_id = msg->amf_ue_ngap_id,
|
||||
.cause.type = NGAP_CAUSE_RADIO_NETWORK,
|
||||
.cause.value = NGAP_CAUSE_RADIO_NETWORK_HO_TARGET_NOT_ALLOWED,
|
||||
};
|
||||
rrc_gNB_send_NGAP_HANDOVER_FAILURE(rrc, &fail);
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint16_t pci = du->setup_req->cell[0].info.nr_pci;
|
||||
LOG_I(NR_RRC, "Received Handover Request (on NR Cell ID=%lu, PCI=%u) \n", msg->nr_cell_id, pci);
|
||||
|
||||
@@ -1169,6 +1208,9 @@ int rrc_gNB_process_Handover_Request(gNB_RRC_INST *rrc, instance_t instance, nga
|
||||
// Store IDs in UE context
|
||||
UE->amf_ue_ngap_id = msg->amf_ue_ngap_id;
|
||||
UE->ue_guami = msg->guami;
|
||||
// Store the serving PLMN
|
||||
UE->serving_plmn = *serving_plmn;
|
||||
|
||||
UE->ho_context->target->ue_ho_prep_info = copy_byte_array(msg->ue_ho_prep_info);
|
||||
// store the received UE Security Capabilities in the UE context
|
||||
FREE_AND_ZERO_BYTE_ARRAY(UE->ue_cap_buffer);
|
||||
|
||||
Reference in New Issue
Block a user