Fix handling of selected PLMN in NGAP

selectedPLMN-Identity IE in RRCSetupComplete is an Index (long) of the PLMN selected
by the UE from the plmn-IdentityInfoList (SIB1) (see 3GPP TS 38.331)

while

Selected PLMN Identity in NGAP INITIAL UE MESSAGE Indicates the selected PLMN
id for the non-3GPP access (PLMN Identity ID carrying MNC, MCC and MNC size,
9.2.5.1 3GPP TS 38.413)

The legacy code was setting Selected PLMN Identity in the latter to an ID
value as the former, however in NGAP the IE to be encoded is PLMN Identity 9.3.3.5

therefore:

* set plmn type plmn_id_t in ngap_nas_first_req_t
* check whether received selectedPLMN-Identity is within bounds
* select PLMN from gNB_RrcConfigurationReq by selectedPLMN-Identity ID
  note: this commit is assuming that the plmn-IdentityInfoList in SIB1
  is matching the PLMN list in gNB_RrcConfigurationReq
* in NGAP: store the PLMN in the NGAP UE context, no need to fetch the PLMN
  in the NGAP instance since the UE context already tells what PLMN
  was selected

Closes #801
This commit is contained in:
Guido Casati
2025-05-08 17:10:50 +02:00
parent 8a0baed430
commit 539d17203f
6 changed files with 45 additions and 51 deletions

View File

@@ -490,22 +490,16 @@ typedef struct ngap_deregistered_gnb_ind_s {
* will be the unique identifier used between RRC and NGAP.
*/
typedef struct ngap_nas_first_req_s {
/* UE id for initial connection to NGAP */
// RAN UE NGAP ID (mandatory)
uint32_t gNB_ue_ngap_id;
/* the chosen PLMN identity as index, see TS 36.331 6.2.2 RRC Connection
* Setup Complete. This index here is zero-based, unlike the standard! */
int selected_plmn_identity;
/* Establishment cause as sent by UE */
/* PLMN: Selected PLMN Identity (optional)
* User Location Information (mandatory) */
plmn_id_t plmn;
// RRC Establishment Cause (mandatory)
ngap_rrc_establishment_cause_t establishment_cause;
/* NAS PDU */
// NAS-PDU (mandatory)
byte_array_t nas_pdu;
/* If this flag is set NGAP layer is expecting the GUAMI. If = 0,
* the temporary s-tmsi is used.
*/
// UE identity: 5G-S-TMSI, GUAMI
ngap_ue_identity_t ue_identity;
} ngap_nas_first_req_t;

View File

@@ -149,12 +149,15 @@ void nr_rrc_pdcp_config_security(gNB_RRC_UE_t *UE, bool enable_ciphering)
}
/** @brief Process AMF Identifier and fill GUAMI struct members */
static nr_guami_t get_guami(const uint32_t amf_Id)
static nr_guami_t get_guami(const uint32_t amf_Id, const plmn_id_t plmn)
{
nr_guami_t guami = {0};
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;
return guami;
}
@@ -179,13 +182,19 @@ void rrc_gNB_send_NGAP_NAS_FIRST_REQ(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, NR_RRC
// NAS-PDU
req->nas_pdu = create_byte_array(rrcSetupComplete->dedicatedNAS_Message.size, rrcSetupComplete->dedicatedNAS_Message.buf);
// Selected PLMN Identity: indicates the selected PLMN id for the non-3GPP access
/* selected_plmn_identity: IE is 1-based, convert to 0-based (C array) */
int selected_plmn_identity = rrcSetupComplete->selectedPLMN_Identity - 1;
if (selected_plmn_identity != 0)
LOG_E(NGAP, "UE %u: sent selected PLMN identity %ld, but only one PLMN supported\n", req->gNB_ue_ngap_id, rrcSetupComplete->selectedPLMN_Identity);
req->selected_plmn_identity = 0; /* always zero because we only support one */
/* 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) {
LOG_E(NGAP,
"Failed to send Initial UE Message: selected PLMN (%ld) identity is out of bounds (%d)\n",
rrcSetupComplete->selectedPLMN_Identity,
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
LOG_I(NGAP, "Selected PLMN in the NG Initial UE Message: MCC %u, MNC %u\n", req->plmn.mcc, req->plmn.mnc);
/* 5G-S-TMSI */
if (UE->Initialue_identity_5g_s_TMSI.presence) {
@@ -204,7 +213,7 @@ void rrc_gNB_send_NGAP_NAS_FIRST_REQ(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, NR_RRC
NR_RegisteredAMF_t *r_amf = rrcSetupComplete->registeredAMF;
req->ue_identity.presenceMask |= NGAP_UE_IDENTITIES_guami;
uint32_t amf_Id = BIT_STRING_to_uint32(&r_amf->amf_Identifier);
UE->ue_guami = req->ue_identity.guami = get_guami(amf_Id);
UE->ue_guami = req->ue_identity.guami = get_guami(amf_Id, req->plmn);
LOG_I(NGAP,
"GUAMI in NGAP_NAS_FIRST_REQ (UE %04x): AMF Set ID %u, Region ID %u, Pointer %u\n",
UE->rnti,

View File

@@ -107,16 +107,15 @@ static ngap_gNB_amf_data_t *select_amf(ngap_gNB_instance_t *instance_p, const ng
if (amf == NULL) {
if (msg->ue_identity.presenceMask & NGAP_UE_IDENTITIES_FiveG_s_tmsi) {
const fiveg_s_tmsi_t *fgs_tmsi = &msg->ue_identity.s_tmsi;
amf = ngap_gNB_nnsf_select_amf_by_amf_setid(instance_p, msg->establishment_cause, msg->selected_plmn_identity, fgs_tmsi->amf_set_id);
amf = ngap_gNB_nnsf_select_amf_by_amf_setid(instance_p, msg->establishment_cause, msg->plmn, fgs_tmsi->amf_set_id);
if (amf) {
NGAP_INFO("UE %d: Chose AMF '%s' (assoc_id %d) through S-TMSI AMFSI %d and selected PLMN Identity index %d MCC %d MNC %d\n",
NGAP_INFO("UE %d: Chose AMF '%s' (assoc_id %d) through S-TMSI AMFSI %d and selected PLMN MCC %d MNC %d\n",
msg->gNB_ue_ngap_id,
amf->amf_name,
amf->assoc_id,
fgs_tmsi->amf_set_id,
msg->selected_plmn_identity,
instance_p->plmn[msg->selected_plmn_identity].plmn.mcc,
instance_p->plmn[msg->selected_plmn_identity].plmn.mnc);
msg->plmn.mcc,
msg->plmn.mnc);
return amf;
}
}
@@ -125,15 +124,14 @@ static ngap_gNB_amf_data_t *select_amf(ngap_gNB_instance_t *instance_p, const ng
// No UE identity (5G-S-TMSI or GUAMI) is present
if (msg->ue_identity.presenceMask == 0) {
// Select the AMF based on the selected PLMN identity received through RRCSetupComplete
amf = ngap_gNB_nnsf_select_amf_by_plmn_id(instance_p, msg->establishment_cause, msg->selected_plmn_identity);
amf = ngap_gNB_nnsf_select_amf_by_plmn_id(instance_p, msg->establishment_cause, msg->plmn);
if (amf) {
NGAP_INFO("UE %d: Chose AMF '%s' (assoc_id %d) through selected PLMN Identity index %d MCC %d MNC %d\n",
NGAP_INFO("UE %d: Chose AMF '%s' (assoc_id %d) through selected PLMN MCC %d MNC %d\n",
msg->gNB_ue_ngap_id,
amf->amf_name,
amf->assoc_id,
msg->selected_plmn_identity,
instance_p->plmn[msg->selected_plmn_identity].plmn.mcc,
instance_p->plmn[msg->selected_plmn_identity].plmn.mnc);
msg->plmn.mcc,
msg->plmn.mnc);
return amf;
} else {
// Select the AMF with the highest capacity
@@ -185,7 +183,7 @@ int ngap_gNB_handle_nas_first_req(instance_t instance, ngap_nas_first_req_t *UEf
ue_desc_p->amf_ref = amf;
ue_desc_p->gNB_ue_ngap_id = UEfirstReq->gNB_ue_ngap_id;
ue_desc_p->gNB_instance = instance_p;
ue_desc_p->selected_plmn_identity = UEfirstReq->selected_plmn_identity;
ue_desc_p->selected_plmn_identity = UEfirstReq->plmn;
ngap_store_ue_context(ue_desc_p);
// RAN UE NGAP ID (M)
@@ -220,7 +218,7 @@ int ngap_gNB_handle_nas_first_req(instance_t instance, ngap_nas_first_req_t *UEf
0, // Cell ID
&userinfo_nr_p->nR_CGI.nRCellIdentity);
plmn_id_t *plmn = &instance_p->plmn[ue_desc_p->selected_plmn_identity].plmn;
plmn_id_t *plmn = &ue_desc_p->selected_plmn_identity;
MCC_MNC_TO_TBCD(plmn->mcc, plmn->mnc, plmn->mnc_digit_length, &userinfo_nr_p->nR_CGI.pLMNIdentity);
/* In case of network sharing,
@@ -444,17 +442,12 @@ int ngap_gNB_nas_uplink(instance_t instance, ngap_uplink_nas_t *ngap_uplink_nas_
MACRO_GNB_ID_TO_CELL_IDENTITY(ngap_gNB_instance_p->gNB_id,
0, // Cell ID
&userinfo_nr_p->nR_CGI.nRCellIdentity);
MCC_MNC_TO_TBCD(ngap_gNB_instance_p->plmn[ue_context_p->selected_plmn_identity].plmn.mcc,
ngap_gNB_instance_p->plmn[ue_context_p->selected_plmn_identity].plmn.mnc,
ngap_gNB_instance_p->plmn[ue_context_p->selected_plmn_identity].plmn.mnc_digit_length,
&userinfo_nr_p->nR_CGI.pLMNIdentity);
plmn_id_t *plmn = &ue_context_p->selected_plmn_identity;
MCC_MNC_TO_TBCD(plmn->mcc, plmn->mnc, plmn->mnc_digit_length, &userinfo_nr_p->nR_CGI.pLMNIdentity);
/* Set TAI */
INT24_TO_OCTET_STRING(ngap_gNB_instance_p->tac, &userinfo_nr_p->tAI.tAC);
MCC_MNC_TO_PLMNID(ngap_gNB_instance_p->plmn[ue_context_p->selected_plmn_identity].plmn.mcc,
ngap_gNB_instance_p->plmn[ue_context_p->selected_plmn_identity].plmn.mnc,
ngap_gNB_instance_p->plmn[ue_context_p->selected_plmn_identity].plmn.mnc_digit_length,
&userinfo_nr_p->tAI.pLMNIdentity);
MCC_MNC_TO_PLMNID(plmn->mcc, plmn->mnc, plmn->mnc_digit_length, &userinfo_nr_p->tAI.pLMNIdentity);
}
if (ngap_gNB_encode_pdu(&pdu, &buffer, &length) < 0) {
NGAP_ERROR("Failed to encode uplink NAS transport\n");

View File

@@ -86,7 +86,7 @@ ngap_gNB_amf_data_t *ngap_gNB_nnsf_select_amf(ngap_gNB_instance_t *instance_p, c
ngap_gNB_amf_data_t *ngap_gNB_nnsf_select_amf_by_plmn_id(ngap_gNB_instance_t *instance_p,
const ngap_rrc_establishment_cause_t cause,
const int selected_plmn_identity)
const plmn_id_t selected_plmn_identity)
{
struct ngap_gNB_amf_data_s *amf_data_p = NULL;
struct ngap_gNB_amf_data_s *amf_highest_capacity_p = NULL;
@@ -132,8 +132,7 @@ ngap_gNB_amf_data_t *ngap_gNB_nnsf_select_amf_by_plmn_id(ngap_gNB_instance_t *in
/* Looking for served GUAMI PLMN Identity selected matching the one provided by the UE */
STAILQ_FOREACH(guami_p, &amf_data_p->served_guami, next) {
STAILQ_FOREACH(served_plmn_p, &guami_p->served_plmns, next) {
if ((served_plmn_p->mcc == instance_p->plmn[selected_plmn_identity].plmn.mcc)
&& (served_plmn_p->mnc == instance_p->plmn[selected_plmn_identity].plmn.mnc)) {
if ((served_plmn_p->mcc == selected_plmn_identity.mcc) && (served_plmn_p->mnc == selected_plmn_identity.mnc)) {
break;
}
}
@@ -155,7 +154,7 @@ ngap_gNB_amf_data_t *ngap_gNB_nnsf_select_amf_by_plmn_id(ngap_gNB_instance_t *in
ngap_gNB_amf_data_t *ngap_gNB_nnsf_select_amf_by_amf_setid(ngap_gNB_instance_t *instance_p,
const ngap_rrc_establishment_cause_t cause,
const int selected_plmn_identity,
const plmn_id_t selected_plmn_identity,
uint8_t amf_setid)
{
struct ngap_gNB_amf_data_s *amf_data_p = NULL;
@@ -203,8 +202,7 @@ ngap_gNB_amf_data_t *ngap_gNB_nnsf_select_amf_by_amf_setid(ngap_gNB_instance_t *
struct plmn_identity_s *served_plmn_p = NULL;
STAILQ_FOREACH(served_plmn_p, &guami_p->served_plmns, next) {
if ((served_plmn_p->mcc == instance_p->plmn[selected_plmn_identity].plmn.mcc)
&& (served_plmn_p->mnc == instance_p->plmn[selected_plmn_identity].plmn.mnc)) {
if ((served_plmn_p->mcc == selected_plmn_identity.mcc) && (served_plmn_p->mnc == selected_plmn_identity.mnc)) {
break;
}
}

View File

@@ -38,11 +38,11 @@ ngap_gNB_amf_data_t *ngap_gNB_nnsf_select_amf(ngap_gNB_instance_t *instance_p, c
ngap_gNB_amf_data_t *ngap_gNB_nnsf_select_amf_by_plmn_id(ngap_gNB_instance_t *instance_p,
const ngap_rrc_establishment_cause_t cause,
const int selected_plmn_identity);
const plmn_id_t selected_plmn_identity);
ngap_gNB_amf_data_t *ngap_gNB_nnsf_select_amf_by_amf_setid(ngap_gNB_instance_t *instance_p,
const ngap_rrc_establishment_cause_t cause,
const int selected_plmn_identity,
const plmn_id_t selected_plmn_identity,
uint8_t amf_setid);
ngap_gNB_amf_data_t *ngap_gNB_nnsf_select_amf_by_guami(ngap_gNB_instance_t *instance_p,

View File

@@ -72,7 +72,7 @@ typedef struct ngap_gNB_ue_context_s {
/* Signaled by the UE in RRC Connection Setup Complete and used in NAS Uplink
* to route NAS messages correctly. 0-based, not 1-based as in TS 36.331
* 6.2.2 RRC Connection Setup Complete! */
int selected_plmn_identity;
plmn_id_t selected_plmn_identity;
/* Reference to gNB data this UE is attached to */
ngap_gNB_instance_t *gNB_instance;