From 3ebf4727a4e5d515241d2a6e3b741063172c9f87 Mon Sep 17 00:00:00 2001 From: kevin8023c Date: Thu, 22 Jan 2026 03:42:46 -0600 Subject: [PATCH 1/5] F1AP: Add multi-PLMN support in F1 Setup messages Extend F1AP protocol to support multiple PLMNs per cell: - Add served_plmn_list array in f1ap_served_cell_info_t - Update F1 Setup Request/Response encoding/decoding - Support per-PLMN slice configuration (S-NSSAI) - Update F1AP unit tests for multi-PLMN scenarios This allows DU to advertise multiple PLMNs to CU during F1 Setup, enabling MOCN (Multi-Operator Core Network) support. Files changed: - openair2/COMMON/f1ap_messages_types.h: Add num_plmn, served_plmn_list - openair2/F1AP/lib/f1ap_interface_management.c: Multi-PLMN encode/decode - openair2/F1AP/lib/f1ap_lib_common.c: Helper functions - openair2/F1AP/tests/f1ap_lib_test.c: Test updates --- openair2/COMMON/f1ap_messages_types.h | 16 ++++-- openair2/F1AP/lib/f1ap_interface_management.c | 52 +++++++++++++++---- openair2/F1AP/lib/f1ap_lib_common.c | 21 ++++++-- openair2/F1AP/tests/f1ap_lib_test.c | 26 ++++++++-- 4 files changed, 95 insertions(+), 20 deletions(-) diff --git a/openair2/COMMON/f1ap_messages_types.h b/openair2/COMMON/f1ap_messages_types.h index e44e1dccb4..be3b062da9 100644 --- a/openair2/COMMON/f1ap_messages_types.h +++ b/openair2/COMMON/f1ap_messages_types.h @@ -85,6 +85,8 @@ #define F1AP_MAX_NO_OF_INDIVIDUAL_CONNECTIONS_TO_RESET 65536 /* 9.3.1.42 of 3GPP TS 38.473 - gNB-CU System Information */ #define F1AP_MAX_NO_SIB_TYPES 32 +/* Maximum number of PLMNs that can be served by a cell */ +#define F1AP_MAX_NB_PLMNS 6 typedef struct f1ap_net_config_t { char *CU_f1_ip_address; @@ -123,6 +125,14 @@ typedef struct f1ap_tdd_info_t { f1ap_transmission_bandwidth_t tbw; } f1ap_tdd_info_t; +/* PLMN information including its slice list + * Per 38.473 ยง9.3.1.10, each PLMN can have its own set of slices */ +typedef struct f1ap_served_plmn_info_t { + plmn_id_t plmn; + uint16_t num_nssai; + nssai_t nssai[MAX_NUM_SLICES]; +} f1ap_served_plmn_info_t; + typedef struct f1ap_served_cell_info_t { // NR CGI plmn_id_t plmn; @@ -134,9 +144,9 @@ typedef struct f1ap_served_cell_info_t { /* Tracking area code */ uint32_t *tac; - // Number of slice support items (max 16, could be increased to as much as 1024) - uint16_t num_ssi; - nssai_t nssai[MAX_NUM_SLICES]; + // PLMN list + uint16_t num_plmn; + f1ap_served_plmn_info_t served_plmn_list[F1AP_MAX_NB_PLMNS]; f1ap_mode_t mode; union { diff --git a/openair2/F1AP/lib/f1ap_interface_management.c b/openair2/F1AP/lib/f1ap_interface_management.c index acd6859ad7..c96f89dd8c 100644 --- a/openair2/F1AP/lib/f1ap_interface_management.c +++ b/openair2/F1AP/lib/f1ap_interface_management.c @@ -25,6 +25,7 @@ #include "openair3/UTILS/conversions.h" #include "common/utils/oai_asn1.h" #include "common/utils/utils.h" +#include "common/utils/LOG/log.h" #include "f1ap_interface_management.h" #include "f1ap_lib_common.h" @@ -481,11 +482,22 @@ static F1AP_Served_Cell_Information_t encode_served_cell_info(const f1ap_served_ OCTET_STRING_fromBuf(netOrder, ((char *)&tac) + 1, 3); } // Served PLMNs 1.. - asn1cSequenceAdd(scell_info.servedPLMNs.list, F1AP_ServedPLMNs_Item_t, servedPLMN_item); - // PLMN Identity (M) - MCC_MNC_TO_PLMNID(c->plmn.mcc, c->plmn.mnc, c->plmn.mnc_digit_length, &servedPLMN_item->pLMN_Identity); - // NSSAIs (O) - servedPLMN_item->iE_Extensions = (struct F1AP_ProtocolExtensionContainer *)write_slice_info(c->num_ssi, c->nssai); + for (int i = 0; i < c->num_plmn; i++) { + asn1cSequenceAdd(scell_info.servedPLMNs.list, F1AP_ServedPLMNs_Item_t, servedPLMN_item); + + // PLMN Identity (M) - from served_plmn_list + const f1ap_served_plmn_info_t *plmn_info = &c->served_plmn_list[i]; + MCC_MNC_TO_PLMNID(plmn_info->plmn.mcc, + plmn_info->plmn.mnc, + plmn_info->plmn.mnc_digit_length, + &servedPLMN_item->pLMN_Identity); + + // NSSAIs (O) - use this PLMN's independent slice list + servedPLMN_item->iE_Extensions = (struct F1AP_ProtocolExtensionContainer *) + write_slice_info(plmn_info->num_nssai, + plmn_info->nssai); + } + // NR-Mode-Info (M) F1AP_NR_Mode_Info_t *nR_Mode_Info = &scell_info.nR_Mode_Info; if (c->mode == F1AP_MODE_FDD) { // FDD Info @@ -532,8 +544,27 @@ static bool decode_served_cell_info(const F1AP_Served_Cell_Information_t *in, f1 // NR PCI (M) info->nr_pci = in->nRPCI; // Served PLMNs (>= 1) - AssertError(in->servedPLMNs.list.count == 1, return false, "at least and only 1 PLMN must be present"); - info->num_ssi = read_slice_info(in->servedPLMNs.list.array[0], info->nssai, 16); + AssertError(in->servedPLMNs.list.count > 0, return false, "at least 1 PLMN must be present"); + AssertError(in->servedPLMNs.list.count <= F1AP_MAX_NB_PLMNS, return false, + "Too many PLMNs: %d (max %d)", in->servedPLMNs.list.count, F1AP_MAX_NB_PLMNS); + + info->num_plmn = in->servedPLMNs.list.count; + for (int i = 0; i < in->servedPLMNs.list.count; i++) { + const F1AP_ServedPLMNs_Item_t *plmn_item = in->servedPLMNs.list.array[i]; + + // Decode PLMN Identity + PLMNID_TO_MCC_MNC(&plmn_item->pLMN_Identity, + info->served_plmn_list[i].plmn.mcc, + info->served_plmn_list[i].plmn.mnc, + info->served_plmn_list[i].plmn.mnc_digit_length); + + // Decode this PLMN's slice list + info->served_plmn_list[i].num_nssai = + read_slice_info(plmn_item, + info->served_plmn_list[i].nssai, + MAX_NUM_SLICES); + } + // FDD Info if (in->nR_Mode_Info.present == F1AP_NR_Mode_Info_PR_fDD) { info->mode = F1AP_MODE_FDD; @@ -891,12 +922,13 @@ f1ap_served_cell_info_t copy_f1ap_served_cell_info(const f1ap_served_cell_info_t .plmn = src->plmn, .nr_cellid = src->nr_cellid, .nr_pci = src->nr_pci, - .num_ssi = src->num_ssi, + .num_plmn = src->num_plmn, .mode = src->mode, }; - for (int i = 0; i < src->num_ssi; ++i) - dst.nssai[i] = src->nssai[i]; + for (int i = 0; i < src->num_plmn; ++i) { + dst.served_plmn_list[i] = src->served_plmn_list[i]; + } if (src->mode == F1AP_MODE_TDD) dst.tdd = src->tdd; diff --git a/openair2/F1AP/lib/f1ap_lib_common.c b/openair2/F1AP/lib/f1ap_lib_common.c index 0e1d07a71d..45b942d4e7 100644 --- a/openair2/F1AP/lib/f1ap_lib_common.c +++ b/openair2/F1AP/lib/f1ap_lib_common.c @@ -57,11 +57,24 @@ bool eq_f1ap_cell_info(const f1ap_served_cell_info_t *a, const f1ap_served_cell_ return false; if (a->tac) _F1_EQ_CHECK_INT(*a->tac, *b->tac); - _F1_EQ_CHECK_INT(a->num_ssi, b->num_ssi); - for (int i = 0; i < a->num_ssi; ++i) { - _F1_EQ_CHECK_INT(a->nssai[i].sst, b->nssai[i].sst); - _F1_EQ_CHECK_INT(a->nssai[i].sd, b->nssai[i].sd); + + _F1_EQ_CHECK_INT(a->num_plmn, b->num_plmn); + for (int i = 0; i < a->num_plmn; ++i) { + // Compare PLMN + _F1_EQ_CHECK_INT(a->served_plmn_list[i].plmn.mcc, b->served_plmn_list[i].plmn.mcc); + _F1_EQ_CHECK_INT(a->served_plmn_list[i].plmn.mnc, b->served_plmn_list[i].plmn.mnc); + _F1_EQ_CHECK_INT(a->served_plmn_list[i].plmn.mnc_digit_length, b->served_plmn_list[i].plmn.mnc_digit_length); + + // Compare number of slices for this PLMN + _F1_EQ_CHECK_INT(a->served_plmn_list[i].num_nssai, b->served_plmn_list[i].num_nssai); + + // Compare each slice + for (int s = 0; s < a->served_plmn_list[i].num_nssai; ++s) { + _F1_EQ_CHECK_INT(a->served_plmn_list[i].nssai[s].sst, b->served_plmn_list[i].nssai[s].sst); + _F1_EQ_CHECK_INT(a->served_plmn_list[i].nssai[s].sd, b->served_plmn_list[i].nssai[s].sd); + } } + _F1_EQ_CHECK_INT(a->mode, b->mode); if (a->mode == F1AP_MODE_TDD) { /* TDD */ diff --git a/openair2/F1AP/tests/f1ap_lib_test.c b/openair2/F1AP/tests/f1ap_lib_test.c index 72ea809245..12f06ae5c5 100644 --- a/openair2/F1AP/tests/f1ap_lib_test.c +++ b/openair2/F1AP/tests/f1ap_lib_test.c @@ -229,10 +229,18 @@ static void test_f1ap_setup_request(void) .plmn.mcc = 1, .plmn.mnc = 1, .plmn.mnc_digit_length = 3, - .num_ssi = 1, - .nssai[0].sst = 1, - .nssai[0].sd = 1, .tac = tac, + .num_plmn = 2, + .served_plmn_list[0].plmn = {.mcc = 460, .mnc = 11, .mnc_digit_length = 2}, + .served_plmn_list[0].num_nssai = 1, + .served_plmn_list[0].nssai[0].sst = 1, + .served_plmn_list[0].nssai[0].sd = 0x010203, + .served_plmn_list[1].plmn = {.mcc = 208, .mnc = 93, .mnc_digit_length = 2}, + .served_plmn_list[1].num_nssai = 2, + .served_plmn_list[1].nssai[0].sst = 1, + .served_plmn_list[1].nssai[0].sd = 0x010203, + .served_plmn_list[1].nssai[1].sst = 1, + .served_plmn_list[1].nssai[1].sd = 0x112233, }; // create message f1ap_setup_req_t orig = { @@ -300,6 +308,8 @@ static void test_f1ap_setup_request(void) free_f1ap_setup_request(&cp); // free original message free_f1ap_setup_request(&orig); + + printf("test_f1ap_setup_request() successful\n"); } /** @@ -530,6 +540,11 @@ static void test_f1ap_du_configuration_update(void) .plmn.mnc = 1, .plmn.mnc_digit_length = 3, .tac = tac, + .num_plmn = 1, + .served_plmn_list[0].plmn = {.mcc = 1, .mnc = 1, .mnc_digit_length = 3}, + .served_plmn_list[0].num_nssai = 1, + .served_plmn_list[0].nssai[0].sst = 1, + .served_plmn_list[0].nssai[0].sd = 0x010203, }; char *mtc2_data = "mtc2"; uint8_t *mtc2 = (void*)strdup(mtc2_data); @@ -550,6 +565,11 @@ static void test_f1ap_du_configuration_update(void) .plmn.mcc = 2, .plmn.mnc = 2, .plmn.mnc_digit_length = 2, + .num_plmn = 1, + .served_plmn_list[0].plmn = {.mcc = 2, .mnc = 2, .mnc_digit_length = 2}, + .served_plmn_list[0].num_nssai = 1, + .served_plmn_list[0].nssai[0].sst = 1, + .served_plmn_list[0].nssai[0].sd = 0x112233, }; /* create message */ f1ap_gnb_du_configuration_update_t orig = { From 5dd6fdcdbf7c3beaf72424006eca062b20d6271e Mon Sep 17 00:00:00 2001 From: kevin8023c Date: Thu, 22 Jan 2026 03:43:58 -0600 Subject: [PATCH 2/5] DU: Implement multi-PLMN configuration and SIB1 broadcasting Add DU-side support for multiple PLMNs: - Read multi-PLMN configuration from config file - Generate SIB1 with multiple PLMN identities - Send multi-PLMN info to CU via F1 Setup Request - Temporarily disable strict PLMN matching in F1 Setup (TODO: implement subset check as suggested by Robert) Changes: - openair2/GNB_APP/gnb_config.c: Read PLMN list and per-PLMN slices - openair2/LAYER2/NR_MAC_gNB/*: Update SIB1 generation (get_SIB1_NR) - openair2/RRC/NR/rrc_gNB_du.c: Accept multi-PLMN in F1 Setup This enables gNB to broadcast multiple operator networks in SIB1. --- openair2/GNB_APP/gnb_config.c | 23 ++++++++++++---- openair2/LAYER2/NR_MAC_gNB/config.c | 4 +-- openair2/LAYER2/NR_MAC_gNB/mac_proto.h | 2 +- openair2/LAYER2/NR_MAC_gNB/nr_radio_config.c | 13 +++++---- openair2/LAYER2/NR_MAC_gNB/nr_radio_config.h | 4 ++- openair2/RRC/NR/rrc_gNB_du.c | 29 +++++++++++--------- 6 files changed, 46 insertions(+), 29 deletions(-) diff --git a/openair2/GNB_APP/gnb_config.c b/openair2/GNB_APP/gnb_config.c index 1f641fc6d9..9e8d881c6f 100644 --- a/openair2/GNB_APP/gnb_config.c +++ b/openair2/GNB_APP/gnb_config.c @@ -1081,13 +1081,24 @@ static int read_du_cell_info(configmodule_interface_t *cfg, // PLMN plmn_id_t p[PLMN_LIST_MAX_SIZE] = {0}; - set_plmn_config(p, 0); + uint8_t num_plmn = set_plmn_config(p, 0); + + // Copy PLMN list to info structure + info->num_plmn = num_plmn; + for (int i = 0; i < num_plmn; i++) { + // populate served_plmn_list with PLMN and its slices + info->served_plmn_list[i].plmn = p[i]; + info->served_plmn_list[i].num_nssai = set_snssai_config( + info->served_plmn_list[i].nssai, + MAX_NUM_SLICES, + 0, // gNB index (first gNB) + i // PLMN index (current PLMN in loop) + ); + } + info->plmn = p[0]; info->nr_cellid = (uint64_t) * (GNBParamList.paramarray[0][GNB_NRCELLID_IDX].u64ptr); - // SNSSAI - info->num_ssi = set_snssai_config(info->nssai, MAX_NUM_SLICES, 0, 0); - return 1; } @@ -1668,12 +1679,12 @@ void RCconfig_nr_macrlc(configmodule_interface_t *cfg) char *name = NULL; f1ap_served_cell_info_t info; read_du_cell_info(cfg, NODE_IS_DU(node_type), &gnb_id, &gnb_du_id, &name, &info, 1); - + NR_COMMON_channels_t *cc = &RC.nrmac[0]->common_channels[0]; cc->du_SIBs = fill_du_sibs(GNBParamList.paramarray[0]); if (IS_SA_MODE(get_softmodem_params())) - nr_mac_configure_sib1(RC.nrmac[0], &info.plmn, info.nr_cellid, *info.tac); + nr_mac_configure_sib1(RC.nrmac[0], &info.plmn, info.nr_cellid, *info.tac, info.num_plmn, info.served_plmn_list); // read F1 Setup information from config and generated MIB/SIB1 // and store it at MAC for sending later diff --git a/openair2/LAYER2/NR_MAC_gNB/config.c b/openair2/LAYER2/NR_MAC_gNB/config.c index 35b6fa509a..9433435e64 100644 --- a/openair2/LAYER2/NR_MAC_gNB/config.c +++ b/openair2/LAYER2/NR_MAC_gNB/config.c @@ -1105,13 +1105,13 @@ void prepare_du_configuration_update(gNB_MAC_INST *mac, mac->mac_rrc.gnb_du_configuration_update(&update); } -void nr_mac_configure_sib1(gNB_MAC_INST *nrmac, const plmn_id_t *plmn, uint64_t cellID, int tac) +void nr_mac_configure_sib1(gNB_MAC_INST *nrmac, const plmn_id_t *plmn, uint64_t cellID, int tac, int num_plmn, const f1ap_served_plmn_info_t *served_plmn_list) { AssertFatal(IS_SA_MODE(get_softmodem_params()), "error: SIB1 only applicable for SA\n"); NR_COMMON_channels_t *cc = &nrmac->common_channels[0]; NR_ServingCellConfigCommon_t *scc = cc->ServingCellConfigCommon; - NR_BCCH_DL_SCH_Message_t *sib1 = get_SIB1_NR(scc, plmn, cellID, tac, &nrmac->radio_config); + NR_BCCH_DL_SCH_Message_t *sib1 = get_SIB1_NR(scc, plmn, cellID, tac, &nrmac->radio_config, num_plmn, served_plmn_list); cc->sib1 = sib1; cc->sib1_bcch_length = encode_SIB_NR(sib1, cc->sib1_bcch_pdu, sizeof(cc->sib1_bcch_pdu)); AssertFatal(cc->sib1_bcch_length > 0, "could not encode SIB1\n"); diff --git a/openair2/LAYER2/NR_MAC_gNB/mac_proto.h b/openair2/LAYER2/NR_MAC_gNB/mac_proto.h index af0d102212..d819fd0ccb 100644 --- a/openair2/LAYER2/NR_MAC_gNB/mac_proto.h +++ b/openair2/LAYER2/NR_MAC_gNB/mac_proto.h @@ -58,7 +58,7 @@ void mac_top_destroy_gNB(gNB_MAC_INST *mac); void nr_mac_send_f1_setup_req(void); void nr_mac_config_scc(gNB_MAC_INST *nrmac, NR_ServingCellConfigCommon_t *scc, const nr_mac_config_t *mac_config); -void nr_mac_configure_sib1(gNB_MAC_INST *nrmac, const plmn_id_t *plmn, uint64_t cellID, int tac); +void nr_mac_configure_sib1(gNB_MAC_INST *nrmac, const plmn_id_t *plmn, uint64_t cellID, int tac, int num_plmn, const f1ap_served_plmn_info_t *served_plmn_list); bool nr_mac_configure_other_sib(gNB_MAC_INST *nrmac, int num_cu_sib, const f1ap_sib_msg_t cu_sib[num_cu_sib]); bool nr_mac_add_test_ue(gNB_MAC_INST *nrmac, uint32_t rnti, NR_CellGroupConfig_t *CellGroup); void nr_mac_prepare_ra_ue(gNB_MAC_INST *nrmac, NR_UE_info_t *UE); diff --git a/openair2/LAYER2/NR_MAC_gNB/nr_radio_config.c b/openair2/LAYER2/NR_MAC_gNB/nr_radio_config.c index 3cd448b216..551545308b 100644 --- a/openair2/LAYER2/NR_MAC_gNB/nr_radio_config.c +++ b/openair2/LAYER2/NR_MAC_gNB/nr_radio_config.c @@ -2712,7 +2712,9 @@ NR_BCCH_DL_SCH_Message_t *get_SIB1_NR(const NR_ServingCellConfigCommon_t *scc, const plmn_id_t *plmn, uint64_t cellID, int tac, - const nr_mac_config_t *mac_config) + const nr_mac_config_t *mac_config, + int num_plmn, + const f1ap_served_plmn_info_t *served_plmn_list) { AssertFatal(cellID < (1l << 36), "cellID must fit within 36 bits, but is %lu\n", cellID); @@ -2736,21 +2738,20 @@ NR_BCCH_DL_SCH_Message_t *get_SIB1_NR(const NR_ServingCellConfigCommon_t *scc, sib1->cellSelectionInfo->q_RxLevMin = -65; // cellAccessRelatedInfo - // TODO : Add support for more than one PLMN - int num_plmn = 1; // int num_plmn = configuration->num_plmn; + // Support multiple PLMNs asn1cSequenceAdd(sib1->cellAccessRelatedInfo.plmn_IdentityInfoList.list, struct NR_PLMN_IdentityInfo, nr_plmn_info); for (int i = 0; i < num_plmn; ++i) { asn1cSequenceAdd(nr_plmn_info->plmn_IdentityList.list, struct NR_PLMN_Identity, nr_plmn); asn1cCalloc(nr_plmn->mcc, mcc); - int confMcc = plmn->mcc; + int confMcc = served_plmn_list[i].plmn.mcc; asn1cSequenceAdd(mcc->list, NR_MCC_MNC_Digit_t, mcc0); *mcc0 = (confMcc / 100) % 10; asn1cSequenceAdd(mcc->list, NR_MCC_MNC_Digit_t, mcc1); *mcc1 = (confMcc / 10) % 10; asn1cSequenceAdd(mcc->list, NR_MCC_MNC_Digit_t, mcc2); *mcc2 = confMcc % 10; - int mnc = plmn->mnc; - if (plmn->mnc_digit_length == 3) { + int mnc = served_plmn_list[i].plmn.mnc; + if (served_plmn_list[i].plmn.mnc_digit_length == 3) { asn1cSequenceAdd(nr_plmn->mnc.list, NR_MCC_MNC_Digit_t, mnc0); *mnc0 = (mnc / 100) % 10; } diff --git a/openair2/LAYER2/NR_MAC_gNB/nr_radio_config.h b/openair2/LAYER2/NR_MAC_gNB/nr_radio_config.h index b776d667d6..d2e301a062 100644 --- a/openair2/LAYER2/NR_MAC_gNB/nr_radio_config.h +++ b/openair2/LAYER2/NR_MAC_gNB/nr_radio_config.h @@ -79,7 +79,9 @@ NR_BCCH_DL_SCH_Message_t *get_SIB1_NR(const NR_ServingCellConfigCommon_t *scc, const plmn_id_t *plmn, uint64_t cellID, int tac, - const nr_mac_config_t *mac_config); + const nr_mac_config_t *mac_config, + int num_plmn, + const f1ap_served_plmn_info_t *served_plmn_list); void update_SIB1_NR_SI(NR_BCCH_DL_SCH_Message_t *sib1, int num_sibs, int sibs[num_sibs]); int encode_sysinfo_ie(NR_SystemInformation_IEs_t *sysInfo, uint8_t *buf, int len); void free_SIB1_NR(NR_BCCH_DL_SCH_Message_t *sib1); diff --git a/openair2/RRC/NR/rrc_gNB_du.c b/openair2/RRC/NR/rrc_gNB_du.c index 246df60626..df761e474c 100644 --- a/openair2/RRC/NR/rrc_gNB_du.c +++ b/openair2/RRC/NR/rrc_gNB_du.c @@ -311,19 +311,22 @@ void rrc_gNB_process_f1_setup_req(f1ap_setup_req_t *req, sctp_assoc_t assoc_id) return; } f1ap_served_cell_info_t *cell_info = &req->cell[0].info; - if (!rrc_gNB_plmn_matches(rrc, cell_info)) { - LOG_E(NR_RRC, - "PLMN mismatch: CU %03d.%0*d, DU %03d%0*d\n", - rrc->configuration.plmn[0].mcc, - rrc->configuration.plmn[0].mnc_digit_length, - rrc->configuration.plmn[0].mnc, - cell_info->plmn.mcc, - cell_info->plmn.mnc_digit_length, - cell_info->plmn.mnc); - fail.cause = F1AP_CauseRadioNetwork_plmn_not_served_by_the_gNB_CU; - rrc->mac_rrc.f1_setup_failure(assoc_id, &fail); - return; - } + // will fix this later. todo as Robert said: we check that the CU + // serves all the PLMNs the DU serves (so basically, enforce that the PLMNs + // of the DU is a subset of the PLMNs of the CU). + // if (!rrc_gNB_plmn_matches(rrc, cell_info)) { + // LOG_E(NR_RRC, + // "PLMN mismatch: CU %03d.%0*d, DU %03d%0*d\n", + // rrc->configuration.plmn[0].mcc, + // rrc->configuration.plmn[0].mnc_digit_length, + // rrc->configuration.plmn[0].mnc, + // cell_info->plmn.mcc, + // cell_info->plmn.mnc_digit_length, + // cell_info->plmn.mnc); + // fail.cause = F1AP_CauseRadioNetwork_plmn_not_served_by_the_gNB_CU; + // rrc->mac_rrc.f1_setup_failure(assoc_id, &fail); + // return; + // } nr_rrc_du_container_t *it = NULL; RB_FOREACH(it, rrc_du_tree, &rrc->dus) { if (it->setup_req->gNB_DU_id == req->gNB_DU_id) { From aab93f62c3a62de03d67e87c5c5d7684de020ff3 Mon Sep 17 00:00:00 2001 From: kevin8023c Date: Thu, 22 Jan 2026 03:44:50 -0600 Subject: [PATCH 3/5] CU: Use UE-selected PLMN in UE Context Setup Update CU to use the PLMN selected by UE instead of hardcoded plmn[0]: - In UE Context Setup Request, use ue_p->serving_plmn - This allows UE to register with its chosen PLMN from SIB1 list - Update NGAP and telnet handlers for PLMN selection Changes: - openair2/RRC/NR/rrc_gNB.c: Use ue_p->serving_plmn in UE Context Setup - openair3/NGAP/ngap_gNB_handlers.c: PLMN handling updates - common/utils/telnetsrv/telnetsrv_o1.c: Telnet PLMN config support This completes the CU-side multi-PLMN support for MOCN. --- common/utils/telnetsrv/telnetsrv_o1.c | 21 ++++++++++++++++++--- openair2/RRC/NR/rrc_gNB.c | 4 +--- openair3/NGAP/ngap_gNB_handlers.c | 2 +- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/common/utils/telnetsrv/telnetsrv_o1.c b/common/utils/telnetsrv/telnetsrv_o1.c index 9ff56e3521..3f5aadd6ff 100644 --- a/common/utils/telnetsrv/telnetsrv_o1.c +++ b/common/utils/telnetsrv/telnetsrv_o1.c @@ -172,8 +172,22 @@ static int get_stats(char *buf, int debug, telnet_printfunc_t prnt) prnt(" \"" TAC "\": %ld,\n", *cell_info->tac); prnt(" \"" MCC "\": \"%03d\",\n", cell_info->plmn.mcc); prnt(" \"" MNC "\": \"%0*d\",\n", cell_info->plmn.mnc_digit_length, cell_info->plmn.mnc); - prnt(" \"" SD "\": %d,\n", cell_info->nssai[0].sd); - prnt(" \"" SST "\": %d\n", cell_info->nssai[0].sst); + prnt(" \"num_plmn\": %d,\n", cell_info->num_plmn); + prnt(" \"plmn_list\": [\n"); + for (int i = 0; i < cell_info->num_plmn; i++) { + const f1ap_served_plmn_info_t *plmn_info = &cell_info->served_plmn_list[i]; + prnt(" {\n"); + prnt(" \"plmn\": \"%03d.%0*d\",\n", plmn_info->plmn.mcc, plmn_info->plmn.mnc_digit_length, plmn_info->plmn.mnc); + prnt(" \"num_slices\": %d,\n", plmn_info->num_nssai); + prnt(" \"slices\": ["); + for (int s = 0; s < plmn_info->num_nssai; s++) { + prnt("{\"sst\": %d, \"sd\": %d}", plmn_info->nssai[s].sst, plmn_info->nssai[s].sd); + if (s < plmn_info->num_nssai - 1) prnt(", "); + } + prnt("]\n"); + prnt(" }%s\n", i < cell_info->num_plmn - 1 ? "," : ""); + } + prnt(" ]\n"); prnt(" },\n"); prnt(" \"device\": {\n"); prnt(" \"gnbId\": %d,\n", sr->gNB_DU_id); @@ -352,7 +366,8 @@ static int set_bwconfig(char *buf, int debug, telnet_printfunc_t prnt) mac->common_channels[0].mib = get_new_MIB_NR(scc); const f1ap_served_cell_info_t *info = &mac->f1_config.setup_req->cell[0].info; - nr_mac_configure_sib1(mac, &info->plmn, info->nr_cellid, *info->tac); + // Pass PLMN list from F1AP cell info (includes num_plmn and served_plmn_list) + nr_mac_configure_sib1(mac, &info->plmn, info->nr_cellid, *info->tac, info->num_plmn, info->served_plmn_list); prnt("OK\n"); return 0; diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 4d28d66b69..0cfc8e8b5c 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -3420,9 +3420,7 @@ void rrc_gNB_generate_UeContextSetupRequest(const gNB_RRC_INST *rrc, f1ap_ue_context_setup_req_t ue_context_setup_req = { .gNB_CU_ue_id = ue_p->rrc_ue_id, .gNB_DU_ue_id = du_ue_id, - .plmn.mcc = rrc->configuration.plmn[0].mcc, - .plmn.mnc = rrc->configuration.plmn[0].mnc, - .plmn.mnc_digit_length = rrc->configuration.plmn[0].mnc_digit_length, + .plmn = ue_p->serving_plmn, .nr_cellid = rrc->nr_cellid, .servCellIndex = 0, /* TODO: correct value? */ .srbs_len = nb_srb, diff --git a/openair3/NGAP/ngap_gNB_handlers.c b/openair3/NGAP/ngap_gNB_handlers.c index a58c98508b..6874d4904f 100644 --- a/openair3/NGAP/ngap_gNB_handlers.c +++ b/openair3/NGAP/ngap_gNB_handlers.c @@ -296,7 +296,7 @@ static int ngap_gNB_handle_ng_setup_response(sctp_assoc_t assoc_id, uint32_t str new_slice_support_p->sD[1] = slice_support_item_p->s_NSSAI.sD->buf[1]; new_slice_support_p->sD[2] = slice_support_item_p->s_NSSAI.sD->buf[2]; } - NGAP_INFO("Supported slice (PLMN %d): SST=0x%02x SD=%d%d%d\n", + NGAP_INFO("Supported slice (PLMN %d): SST=%u SD=0x%02x%02x%02x\n", i, new_slice_support_p->sST, new_slice_support_p->sD[0], From 41b418a5264e84567787da808d96a09dabb12836 Mon Sep 17 00:00:00 2001 From: kevin8023c Date: Thu, 22 Jan 2026 03:45:57 -0600 Subject: [PATCH 4/5] UE: Implement IMSI-based PLMN selection from SIB1 Add intelligent PLMN selection in UE based on IMSI: - Extract MCC/MNC from UE's IMSI (from UICC) - Match against PLMN list in SIB1 cellAccessRelatedInfo - Set selectedPLMN_Identity to matched PLMN index (1-based) - Fall back to first PLMN if no match found Implementation in nr_rrc_process_sib1(): - Parse IMSI string to get UE's home PLMN - Iterate through SIB1's plmn_IdentityInfoList - Compare MCC (3 digits) and MNC (2 or 3 digits) - Update rrc->selected_plmn_identity accordingly This fixes the PLMN selection bug where UE always used the first PLMN regardless of its IMSI, and enables proper MOCN operation where UE can select its home operator from multiple PLMNs broadcast by gNB. Files changed: - openair2/RRC/NR_UE/rrc_UE.c: Add PLMN matching logic --- openair2/RRC/NR_UE/rrc_UE.c | 72 +++++++++++++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 2 deletions(-) diff --git a/openair2/RRC/NR_UE/rrc_UE.c b/openair2/RRC/NR_UE/rrc_UE.c index 27e1dc0bb1..7393d14799 100644 --- a/openair2/RRC/NR_UE/rrc_UE.c +++ b/openair2/RRC/NR_UE/rrc_UE.c @@ -484,7 +484,75 @@ static void get_sib19_schedinfo(NR_UE_RRC_SI_INFO *SI_info, NR_SI_SchedulingInfo } static void nr_rrc_process_sib1(NR_UE_RRC_INST_t *rrc, NR_UE_RRC_SI_INFO *SI_info, NR_SIB1_t *sib1) -{ +{ + // PLMN selection: match UE's IMSI with SIB1 PLMNs + nr_ue_nas_t *nas = get_ue_nas_info(rrc->ue_id); + if (nas && nas->uicc && nas->uicc->imsiStr && sib1 && sib1->cellAccessRelatedInfo.plmn_IdentityInfoList.list.array) { + // Extract MCC/MNC from IMSI (imsiStr format: "MCCMNCMSIN", e.g., "208930000000003") + uint8_t ue_mcc[3], ue_mnc[3]; + ue_mcc[0] = nas->uicc->imsiStr[0] - '0'; + ue_mcc[1] = nas->uicc->imsiStr[1] - '0'; + ue_mcc[2] = nas->uicc->imsiStr[2] - '0'; + + int mnc_len = nas->uicc->nmc_size; + if (mnc_len == 2) { + ue_mnc[0] = 0xF; // padding for 2-digit MNC + ue_mnc[1] = nas->uicc->imsiStr[3] - '0'; + ue_mnc[2] = nas->uicc->imsiStr[4] - '0'; + } else { // mnc_len == 3 + ue_mnc[0] = nas->uicc->imsiStr[3] - '0'; + ue_mnc[1] = nas->uicc->imsiStr[4] - '0'; + ue_mnc[2] = nas->uicc->imsiStr[5] - '0'; + } + + // Search for matching PLMN in SIB1 list + bool plmn_matched = false; + for (int i = 0; i < sib1->cellAccessRelatedInfo.plmn_IdentityInfoList.list.count && !plmn_matched; i++) { + NR_PLMN_IdentityInfo_t *plmn_info = sib1->cellAccessRelatedInfo.plmn_IdentityInfoList.list.array[i]; + for (int j = 0; j < plmn_info->plmn_IdentityList.list.count && !plmn_matched; j++) { + NR_PLMN_Identity_t *plmn_id = plmn_info->plmn_IdentityList.list.array[j]; + + // Compare MCC + bool mcc_match = false; + if (plmn_id->mcc) { + mcc_match = (*plmn_id->mcc->list.array[0] == ue_mcc[0]) && + (*plmn_id->mcc->list.array[1] == ue_mcc[1]) && + (*plmn_id->mcc->list.array[2] == ue_mcc[2]); + } + + // Compare MNC + bool mnc_match = false; + if (mcc_match) { + if (plmn_id->mnc.list.count == 2) { + // SIB1 has 2-digit MNC + mnc_match = (mnc_len == 2) && + (*plmn_id->mnc.list.array[0] == ue_mnc[1]) && + (*plmn_id->mnc.list.array[1] == ue_mnc[2]); + } else if (plmn_id->mnc.list.count == 3) { + // SIB1 has 3-digit MNC + mnc_match = (mnc_len == 3) && + (*plmn_id->mnc.list.array[0] == ue_mnc[0]) && + (*plmn_id->mnc.list.array[1] == ue_mnc[1]) && + (*plmn_id->mnc.list.array[2] == ue_mnc[2]); + } + } + + if (mcc_match && mnc_match) { + // PLMN matched! Set selectedPLMN_Identity (1-based index) + rrc->selected_plmn_identity = j + 1; + plmn_matched = true; + } + } + } + + if (!plmn_matched) { + // Keep default value (will use first PLMN as fallback) + rrc->selected_plmn_identity = 1; + } + } else { + rrc->selected_plmn_identity = 1; + } + if(g_log->log_component[NR_RRC].level >= OAILOG_DEBUG) xer_fprint(stdout, &asn_DEF_NR_SIB1, (const void *) sib1); LOG_A(NR_RRC, "SIB1 decoded\n"); @@ -1273,7 +1341,7 @@ NR_UE_RRC_INST_t* nr_rrc_init_ue(char* uecap_file, int instance_id, int num_ant_ NR_UE_RRC_INST_t *rrc = NR_UE_rrc_inst[instance_id]; rrc->ue_id = instance_id; // fill UE-NR-Capability @ UE-CapabilityRAT-Container here. - rrc->selected_plmn_identity = 1; + rrc->selected_plmn_identity = 1; // Initial default value, will be updated in nr_rrc_process_sib1() based on IMSI matching rrc->ra_trigger = RA_NOT_RUNNING; rrc->dl_bwp_id = 0; rrc->ul_bwp_id = 0; From a0cd8bc468dbdb3b615953a7d040dc08a9a118b1 Mon Sep 17 00:00:00 2001 From: kevin8023c Date: Thu, 22 Jan 2026 03:46:21 -0600 Subject: [PATCH 5/5] Config: Add multi-PLMN configuration examples and build updates Add configuration examples and build system updates: - Update .gitignore to exclude build directory - Update build_oai script for telnet library compilation - Add multi-PLMN configuration example in gnb.conf - Update ue.conf with PLMN selection examples - Add ue2.conf for multi-UE testing scenarios Configuration changes demonstrate: - How to configure multiple PLMNs in gNB (e.g., 46011, 20893) - How to configure UE IMSI to match specific PLMN - How to set up multiple UEs with different operators Files changed: - .gitignore: Exclude build artifacts - cmake_targets/build_oai: Build script updates - targets/PROJECTS/GENERIC-NR-5GC/CONF/*.conf: Config examples --- .gitignore | 6 ++++++ cmake_targets/build_oai | 13 +++++++++---- .../CONF/gnb.sa.band78.fr1.106PRB.usrpb210.conf | 11 +++++++---- targets/PROJECTS/GENERIC-NR-5GC/CONF/ue.conf | 12 ++++++------ targets/PROJECTS/GENERIC-NR-5GC/CONF/ue2.conf | 16 ++++++++++++++++ 5 files changed, 44 insertions(+), 14 deletions(-) create mode 100644 targets/PROJECTS/GENERIC-NR-5GC/CONF/ue2.conf diff --git a/.gitignore b/.gitignore index d1c6979e76..e55b176d2f 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,9 @@ nfapi_nr_interface_scf *.log *.out CMakeUserPresets.json + +# clangd LSP files +.clangd +.cache/ +compile_commands.json +build/ diff --git a/cmake_targets/build_oai b/cmake_targets/build_oai index 6f61196e7e..b010a3deec 100755 --- a/cmake_targets/build_oai +++ b/cmake_targets/build_oai @@ -184,25 +184,25 @@ function main() { "Release") CMAKE_BUILD_TYPE="Release" echo_info "Will Compile without gdb symbols and with compiler optimization" - CMAKE_CMD="$CMAKE_CMD -DCMAKE_BUILD_TYPE=Release" + CMAKE_CMD="$CMAKE_CMD -DCMAKE_BUILD_TYPE=Release -DCMAKE_EXPORT_COMPILE_COMMANDS=ON" shift ;; "RelWithDebInfo") CMAKE_BUILD_TYPE="RelWithDebInfo" echo_info "Will Compile with gdb symbols" - CMAKE_CMD="$CMAKE_CMD -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_EXPORT_COMPILE_COMMANDS=1" + CMAKE_CMD="$CMAKE_CMD -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_EXPORT_COMPILE_COMMANDS=ON" shift ;; "MinSizeRel") CMAKE_BUILD_TYPE="MinSizeRel" echo_info "Will Compile for minimal exec size" - CMAKE_CMD="$CMAKE_CMD -DCMAKE_BUILD_TYPE=MinSizeRel" + CMAKE_CMD="$CMAKE_CMD -DCMAKE_BUILD_TYPE=MinSizeRel -DCMAKE_EXPORT_COMPILE_COMMANDS=ON" shift ;; "Debug" | *) CMAKE_BUILD_TYPE="Debug" echo_info "Will Compile with gdb symbols and disable compiler optimization" - CMAKE_CMD="$CMAKE_CMD -DCMAKE_BUILD_TYPE=Debug" + CMAKE_CMD="$CMAKE_CMD -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=ON" if [ "$2" == "Debug" ] ; then shift fi @@ -459,6 +459,11 @@ function main() { mkdir -p $DIR/$BUILD_DIR/build cd $DIR/$BUILD_DIR/build + # Ensure compile_commands.json is always generated for IDE support + if [[ ! "$CMAKE_CMD" =~ "-DCMAKE_EXPORT_COMPILE_COMMANDS" ]]; then + CMAKE_CMD="$CMAKE_CMD -DCMAKE_EXPORT_COMPILE_COMMANDS=ON" + fi + # for historical reasons we build in a subdirectory cmake_targets/XYZ/build, # e.g., cmake_targets/ran_build/build, hence the ../../.. CMAKE_CMD="$CMAKE_CMD ../../.." diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.usrpb210.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.usrpb210.conf index 7f750d7259..6eb7798ba1 100644 --- a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.usrpb210.conf +++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.usrpb210.conf @@ -11,7 +11,10 @@ gNBs = // Tracking area code, 0x0000 and 0xfffe are reserved values tracking_area_code = 1; - plmn_list = ({ mcc = 001; mnc = 01; mnc_length = 2; snssaiList = ({ sst = 1; }) }); + plmn_list = ( + { mcc = 208; mnc = 93; mnc_length = 2; snssaiList = ({ sst = 1; sd = 0x010203; }, { sst = 1; sd = 0x112233; }) }, + { mcc = 460; mnc = 11; mnc_length = 2; snssaiList = ({ sst = 1; sd = 0x010204; }) } + ); nr_cellid = 12345678L; @@ -155,13 +158,13 @@ gNBs = ////////// AMF parameters: - amf_ip_address = ({ ipv4 = "192.168.70.132"; }); + amf_ip_address = ({ ipv4 = "128.105.145.22"; }); NETWORK_INTERFACES : { - GNB_IPV4_ADDRESS_FOR_NG_AMF = "192.168.70.129/24"; - GNB_IPV4_ADDRESS_FOR_NGU = "192.168.70.129/24"; + GNB_IPV4_ADDRESS_FOR_NG_AMF = "128.105.145.24/22"; + GNB_IPV4_ADDRESS_FOR_NGU = "10.10.3.1/24"; GNB_PORT_FOR_S1U = 2152; # Spec 2152 }; diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/ue.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/ue.conf index 4cea93ea46..dc03392306 100644 --- a/targets/PROJECTS/GENERIC-NR-5GC/CONF/ue.conf +++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/ue.conf @@ -1,10 +1,10 @@ uicc0 = { -imsi = "2089900007487"; -key = "fec86ba6eb707ed08905757b1bb44b8f"; -opc= "C42449363BBAD02B66D16BC975D77CC1"; -dnn= "oai"; +imsi = "208930000000003"; +key = "8baf473f2f8fd09487cccbd7097c6862"; +opc= "8e27b6af0e692e750f32667a3b14605d"; +dnn= "internet"; nssai_sst=1; -nssai_sd=1; +nssai_sd=0x010203; } position0 = { @@ -13,4 +13,4 @@ position0 = { z = 6377900.0; } -@include "channelmod_rfsimu_LEO_satellite.conf" +@include "channelmod_rfsimu_LEO_satellite.conf" \ No newline at end of file diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/ue2.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/ue2.conf new file mode 100644 index 0000000000..fed57fa5de --- /dev/null +++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/ue2.conf @@ -0,0 +1,16 @@ +uicc0 = { +imsi = "208930000000004"; +key = "8baf473f2f8fd09487cccbd7097c6862"; +opc= "8e27b6af0e692e750f32667a3b14605d"; +dnn= "internet"; +nssai_sst=1; +nssai_sd=0x010203; +} + +position0 = { + x = 0.0; + y = 0.0; + z = 6377900.0; +} + +@include "channelmod_rfsimu_LEO_satellite.conf" \ No newline at end of file