/* KPI (statistics, counters) at RANAP level */ /* (C) 2024 by Harald Welte * All Rights Reserved * * SPDX-License-Identifier: AGPL-3.0+ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ #include "config.h" #include #include #include #include #include /*********************************************************************** * DOWNLINK messages ***********************************************************************/ static void kpi_ranap_process_dl_iu_rel_cmd(struct hnbgw_context_map *map, const ranap_message *ranap) { struct hnb_persistent *hnbp = map->hnb_ctx->persistent; const RANAP_Cause_t *cause; OSMO_ASSERT(ranap->procedureCode == RANAP_ProcedureCode_id_Iu_Release); cause = &ranap->msg.iu_ReleaseCommandIEs.cause; /* When Iu is released, all RABs are released implicitly */ for (unsigned int i = 0; i < ARRAY_SIZE(map->rab_state); i++) { unsigned int ctr_num; switch (map->rab_state[i]) { case RAB_STATE_ACTIVE: if (cause->present == RANAP_Cause_PR_nAS || cause->choice.nAS == RANAP_CauseNAS_normal_release) { ctr_num = HNB_CTR_RANAP_PS_RAB_REL_IMPLICIT; } else { ctr_num = HNB_CTR_RANAP_PS_RAB_REL_IMPLICIT_ABNORMAL; } if (!map->is_ps) ctr_num++; HNBP_CTR_INC(hnbp, ctr_num); break; } } /* clear all RAB state */ memset(map->rab_state, 0, sizeof(map->rab_state)); } static void kpi_ranap_process_dl_rab_ass_req(struct hnbgw_context_map *map, ranap_message *ranap) { struct hnb_persistent *hnbp = map->hnb_ctx->persistent; RANAP_RAB_AssignmentRequestIEs_t *ies; int rc; OSMO_ASSERT(ranap->procedureCode == RANAP_ProcedureCode_id_RAB_Assignment); ies = &ranap->msg.raB_AssignmentRequestIEs; if (ies->presenceMask & RAB_ASSIGNMENTREQUESTIES_RANAP_RAB_SETUPORMODIFYLIST_PRESENT) { RANAP_RAB_SetupOrModifyList_t *som_list = &ies->raB_SetupOrModifyList; for (unsigned int i = 0; i < som_list->list.count; i++) { RANAP_ProtocolIE_ContainerPair_t *container_pair = som_list->list.array[i]; RANAP_ProtocolIE_FieldPair_t *field_pair = container_pair->list.array[0]; RANAP_RAB_SetupOrModifyItemFirst_t _rab_setup_or_modify_item_first = {}; RANAP_RAB_SetupOrModifyItemFirst_t *rab_setup_or_modify_item_first = &_rab_setup_or_modify_item_first; uint8_t rab_id; if (!field_pair) continue; if (field_pair->id != RANAP_ProtocolIE_ID_id_RAB_SetupOrModifyItem) continue; rc = ranap_decode_rab_setupormodifyitemfirst(rab_setup_or_modify_item_first, &field_pair->firstValue); if (rc < 0) continue; /* RAB-ID is an 8-bit bit-string, so it's the first byte */ rab_id = rab_setup_or_modify_item_first->rAB_ID.buf[0]; /* the only way to distinguish a "setup" from a "modify" is to know which RABs are * already established. If it's already established, it is a modification; if it's * new, it is a setup */ switch (map->rab_state[rab_id]) { case RAB_STATE_ACTIVE: HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_MOD_REQ : HNB_CTR_RANAP_CS_RAB_MOD_REQ); break; case RAB_STATE_INACTIVE: HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_ACT_REQ : HNB_CTR_RANAP_CS_RAB_ACT_REQ); map->rab_state[rab_id] = RAB_STATE_ACT_REQ; break; default: LOG_MAP(map, DRANAP, LOGL_NOTICE, "Unexpected RAB Activation/Modification Req for RAB in state %u\n", map->rab_state[rab_id]); break; } ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_SetupOrModifyItemFirst, rab_setup_or_modify_item_first); } } if (ies->presenceMask & RAB_ASSIGNMENTREQUESTIES_RANAP_RAB_RELEASELIST_PRESENT) { RANAP_RAB_ReleaseList_t *r_list = &ies->raB_ReleaseList; for (unsigned int i = 0; i < r_list->raB_ReleaseList_ies.list.count; i++) { RANAP_IE_t *release_list_ie = r_list->raB_ReleaseList_ies.list.array[i]; RANAP_RAB_ReleaseItemIEs_t _rab_rel_item_ies = {}; RANAP_RAB_ReleaseItemIEs_t *rab_rel_item_ies = &_rab_rel_item_ies; RANAP_RAB_ReleaseItem_t *rab_rel_item; uint8_t rab_id; if (!release_list_ie) continue; if (release_list_ie->id != RANAP_ProtocolIE_ID_id_RAB_ReleaseItem) continue; rc = ranap_decode_rab_releaseitemies_fromlist(rab_rel_item_ies, &release_list_ie->value); if (rc < 0) continue; rab_rel_item = &rab_rel_item_ies->raB_ReleaseItem; /* RAB-ID is an 8-bit bit-string, so it's the first byte */ rab_id = rab_rel_item->rAB_ID.buf[0]; switch (map->rab_state[rab_id]) { case RAB_STATE_ACTIVE: unsigned int ctr_num; if (rab_rel_item->cause.present == RANAP_Cause_PR_nAS && rab_rel_item->cause.choice.nAS == RANAP_CauseNAS_normal_release) { ctr_num = HNB_CTR_RANAP_PS_RAB_REL_REQ; } else { ctr_num = HNB_CTR_RANAP_PS_RAB_REL_REQ_ABNORMAL; } if (!map->is_ps) ctr_num++; HNBP_CTR_INC(hnbp, ctr_num); break; default: LOG_MAP(map, DRANAP, LOGL_NOTICE, "Unexpected RAB Release Req in state %u\n", map->rab_state[rab_id]); break; } /* mark that RAB as release requested */ map->rab_state[rab_id] = RAB_STATE_REL_REQ; ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_ReleaseItem, rab_rel_item_ies); } } } void kpi_ranap_process_dl(struct hnbgw_context_map *map, ranap_message *ranap) { switch (ranap->procedureCode) { case RANAP_ProcedureCode_id_RAB_Assignment: /* RAB ASSIGNMENT REQ (8.2) */ kpi_ranap_process_dl_rab_ass_req(map, ranap); break; case RANAP_ProcedureCode_id_Iu_Release: kpi_ranap_process_dl_iu_rel_cmd(map, ranap); /* IU RELEASE CMD (8.5) */ break; default: break; } } /*********************************************************************** * UPLINK messages ***********************************************************************/ static void kpi_ranap_process_ul_rab_ass_resp(struct hnbgw_context_map *map, ranap_message *ranap) { struct hnb_persistent *hnbp = map->hnb_ctx->persistent; RANAP_RAB_AssignmentResponseIEs_t *ies; int rc; OSMO_ASSERT(ranap->procedureCode == RANAP_ProcedureCode_id_RAB_Assignment); ies = &ranap->msg.raB_AssignmentResponseIEs; if (ies->presenceMask & RAB_ASSIGNMENTRESPONSEIES_RANAP_RAB_SETUPORMODIFIEDLIST_PRESENT) { RANAP_RAB_SetupOrModifiedList_t *som_list = &ies->raB_SetupOrModifiedList; for (unsigned int i = 0; i < som_list->raB_SetupOrModifiedList_ies.list.count; i++) { RANAP_IE_t *som_list_ie = som_list->raB_SetupOrModifiedList_ies.list.array[i]; RANAP_RAB_SetupOrModifiedItemIEs_t _rab_som_item_ies = {}; RANAP_RAB_SetupOrModifiedItemIEs_t *rab_som_item_ies = &_rab_som_item_ies; RANAP_RAB_SetupOrModifiedItem_t *rab_som_item; uint8_t rab_id; if (!som_list_ie) continue; if (som_list_ie->id != RANAP_ProtocolIE_ID_id_RAB_SetupOrModifiedItem) continue; rc = ranap_decode_rab_setupormodifieditemies_fromlist(rab_som_item_ies, &som_list_ie->value); if (rc < 0) continue; rab_som_item = &rab_som_item_ies->raB_SetupOrModifiedItem; /* RAB-ID is an 8-bit bit-string, so it's the first byte */ rab_id = rab_som_item->rAB_ID.buf[0]; /* differentiate modify / activate */ switch (map->rab_state[rab_id]) { case RAB_STATE_ACT_REQ: HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_ACT_CNF : HNB_CTR_RANAP_CS_RAB_ACT_CNF); map->rab_state[rab_id] = RAB_STATE_ACTIVE; break; case RAB_STATE_ACTIVE: HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_MOD_CNF : HNB_CTR_RANAP_CS_RAB_MOD_CNF); break; default: LOG_MAP(map, DRANAP, LOGL_NOTICE, "Unexpected RAB Activation/Modification Conf for RAB in state %u\n", map->rab_state[rab_id]); break; } ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_SetupOrModifiedItem, rab_som_item_ies); } } if (ies->presenceMask & RAB_ASSIGNMENTRESPONSEIES_RANAP_RAB_RELEASEDLIST_PRESENT) { RANAP_RAB_ReleasedList_t *r_list = &ies->raB_ReleasedList; /* increment number of released RABs, we don't need to do that individually during iteration */ HNBP_CTR_ADD(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_REL_CNF : HNB_CTR_RANAP_CS_RAB_REL_CNF, r_list->raB_ReleasedList_ies.list.count); for (unsigned int i = 0; i < r_list->raB_ReleasedList_ies.list.count; i++) { RANAP_IE_t *released_list_ie = r_list->raB_ReleasedList_ies.list.array[i]; RANAP_RAB_ReleasedItemIEs_t _rab_rel_item_ies = {}; RANAP_RAB_ReleasedItemIEs_t *rab_rel_item_ies = &_rab_rel_item_ies; RANAP_RAB_ReleasedItem_t *rab_rel_item; uint8_t rab_id; if (!released_list_ie) continue; if (released_list_ie->id != RANAP_ProtocolIE_ID_id_RAB_ReleasedItem) continue; rc = ranap_decode_rab_releaseditemies_fromlist(rab_rel_item_ies, &released_list_ie->value); if (rc < 0) continue; rab_rel_item = &rab_rel_item_ies->raB_ReleasedItem; /* RAB-ID is an 8-bit bit-string, so it's the first byte */ rab_id = rab_rel_item->rAB_ID.buf[0]; switch (map->rab_state[rab_id]) { case RAB_STATE_REL_REQ: break; default: LOG_MAP(map, DRANAP, LOGL_NOTICE, "Unexpected RAB Release Conf for RAB in state %u\n", map->rab_state[rab_id]); break; } /* mark that RAB as released */ map->rab_state[rab_id] = RAB_STATE_INACTIVE; ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_ReleasedItem, rab_rel_item_ies); } } if (ies->presenceMask & RAB_ASSIGNMENTRESPONSEIES_RANAP_RAB_QUEUEDLIST_PRESENT) LOG_MAP(map, DRANAP, LOGL_NOTICE, "RAB Activation has been queued; we don't support KPIs for this\n"); if (ies->presenceMask & RAB_ASSIGNMENTRESPONSEIES_RANAP_RAB_FAILEDLIST_PRESENT) { RANAP_RAB_FailedList_t *f_list = &ies->raB_FailedList; for (unsigned int i = 0; i < f_list->raB_FailedList_ies.list.count; i++) { RANAP_IE_t *failed_list_ie = f_list->raB_FailedList_ies.list.array[i]; RANAP_RAB_FailedItemIEs_t _rab_failed_item_ies = {}; RANAP_RAB_FailedItemIEs_t *rab_failed_item_ies = &_rab_failed_item_ies; RANAP_RAB_FailedItem_t *rab_failed_item; uint8_t rab_id; if (!failed_list_ie) continue; if (failed_list_ie->id != RANAP_ProtocolIE_ID_id_RAB_FailedItem) continue; rc = ranap_decode_rab_faileditemies_fromlist(rab_failed_item_ies, &failed_list_ie->value); if (rc < 0) continue; rab_failed_item = &rab_failed_item_ies->raB_FailedItem; /* RAB-ID is an 8-bit bit-string, so it's the first byte */ rab_id = rab_failed_item->rAB_ID.buf[0]; /* differentiate modify / activate */ switch (map->rab_state[rab_id]) { case RAB_STATE_ACT_REQ: HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_ACT_FAIL : HNB_CTR_RANAP_CS_RAB_ACT_FAIL); map->rab_state[rab_id] = RAB_STATE_INACTIVE; break; case RAB_STATE_ACTIVE: HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_MOD_FAIL : HNB_CTR_RANAP_CS_RAB_MOD_FAIL); // FIXME: does it remain active after modification failure? break; default: LOG_MAP(map, DRANAP, LOGL_NOTICE, "Unexpected RAB Activation/Modification Failed for RAB in state %u\n", map->rab_state[rab_id]); break; } ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_FailedItem, rab_failed_item_ies); } } if (ies->presenceMask & RAB_ASSIGNMENTRESPONSEIES_RANAP_RAB_RELEASEFAILEDLIST_PRESENT) { RANAP_RAB_ReleaseFailedList_t *rf_list = &ies->raB_ReleaseFailedList; /* increment number of released RABs, we don't need to do that individually during iteration */ HNBP_CTR_ADD(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_REL_FAIL : HNB_CTR_RANAP_CS_RAB_REL_FAIL, rf_list->raB_FailedList_ies.list.count); for (unsigned int i = 0; i < rf_list->raB_FailedList_ies.list.count; i++) { RANAP_IE_t *failed_list_ie = rf_list->raB_FailedList_ies.list.array[i]; RANAP_RAB_FailedItemIEs_t _rab_failed_item_ies = {}; RANAP_RAB_FailedItemIEs_t *rab_failed_item_ies = &_rab_failed_item_ies; RANAP_RAB_FailedItem_t *rab_failed_item; uint8_t rab_id; if (!failed_list_ie) continue; if (failed_list_ie->id != RANAP_ProtocolIE_ID_id_RAB_FailedItem) continue; rc = ranap_decode_rab_faileditemies_fromlist(rab_failed_item_ies, &failed_list_ie->value); if (rc < 0) continue; rab_failed_item = &rab_failed_item_ies->raB_FailedItem; /* RAB-ID is an 8-bit bit-string, so it's the first byte */ rab_id = rab_failed_item->rAB_ID.buf[0]; /* differentiate modify / activate */ switch (map->rab_state[rab_id]) { case RAB_STATE_ACT_REQ: HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_ACT_FAIL : HNB_CTR_RANAP_CS_RAB_ACT_FAIL); map->rab_state[rab_id] = RAB_STATE_INACTIVE; break; case RAB_STATE_ACTIVE: HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_MOD_FAIL : HNB_CTR_RANAP_CS_RAB_MOD_FAIL); // FIXME: does it remain active after modification failure? break; default: LOG_MAP(map, DRANAP, LOGL_NOTICE, "Unexpected RAB Release Failed for RAB in state %u\n", map->rab_state[rab_id]); break; } ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_FailedItem, rab_failed_item_ies); } } } void kpi_ranap_process_ul(struct hnbgw_context_map *map, ranap_message *ranap) { switch (ranap->procedureCode) { case RANAP_ProcedureCode_id_RAB_Assignment: /* RAB ASSIGNMENT REQ (8.2) */ kpi_ranap_process_ul_rab_ass_resp(map, ranap); break; case RANAP_ProcedureCode_id_Iu_Release: /* TODO: We might want to parse the list of released RABs here and then mark each of those as * released. For now we simply assume that all RABs are released in IU RELEASE during * processing of the downlink Iu Release Command. It's not like the RNC/HNB has any way to * refuse the release anyway. */ break; default: break; } }