/* OsmoHNBGW implementation of CS and PS Level3 message decoding (NAS PDU) */ /* Copyright 2023 by sysmocom - s.f.m.c. GmbH * All Rights Reserved * * Author: Neels Janosch Hofmeyr * * 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 "asn1helpers.h" #include #include #include #include #include #include static const struct tlv_definition gsm48_gmm_att_tlvdef = { .def = { [GSM48_IE_GMM_CIPH_CKSN] = { TLV_TYPE_FIXED, 1 }, [GSM48_IE_GMM_TIMER_READY] = { TLV_TYPE_TV, 1 }, [GSM48_IE_GMM_TMSI_BASED_NRI_C] = { TLV_TYPE_TLV }, [GSM48_IE_GMM_ALLOC_PTMSI] = { TLV_TYPE_TLV, 0 }, [GSM48_IE_GMM_PTMSI_SIG] = { TLV_TYPE_FIXED, 3 }, [GSM48_IE_GMM_AUTH_RAND] = { TLV_TYPE_FIXED, 16 }, [GSM48_IE_GMM_AUTH_SRES] = { TLV_TYPE_FIXED, 4 }, [GSM48_IE_GMM_IMEISV] = { TLV_TYPE_TLV, 0 }, [GSM48_IE_GMM_DRX_PARAM] = { TLV_TYPE_FIXED, 2 }, [GSM48_IE_GMM_MS_NET_CAPA] = { TLV_TYPE_TLV, 0 }, [GSM48_IE_GMM_PDP_CTX_STATUS] = { TLV_TYPE_TLV, 0 }, [GSM48_IE_GMM_PS_LCS_CAPA] = { TLV_TYPE_TLV, 0 }, [GSM48_IE_GMM_GMM_MBMS_CTX_ST] = { TLV_TYPE_TLV, 0 }, }, }; static void decode_gmm_tlv(struct osmo_mobile_identity *mi, struct osmo_routing_area_id *old_ra, int *nri, const uint8_t *tlv_data, size_t tlv_len, bool allow_hex) { struct tlv_parsed tp; struct tlv_p_entry *e; tlv_parse(&tp, &gsm48_gmm_att_tlvdef, tlv_data, tlv_len, 0, 0); e = TLVP_GET(&tp, GSM48_IE_GMM_TMSI_BASED_NRI_C); if (e) { *nri = e->val[0]; *nri <<= 2; *nri |= e->val[1] >> 6; } } static int mobile_identity_decode_from_gmm_att_req(struct osmo_mobile_identity *mi, struct osmo_routing_area_id *old_ra, int *nri, const uint8_t *l3_data, size_t l3_len, bool allow_hex) { const struct gsm48_hdr *gh = (void *)l3_data; const uint8_t *cur = gh->data; const uint8_t *end = l3_data + l3_len; const uint8_t *mi_data; uint8_t mi_len; uint8_t msnc_len; uint8_t ms_ra_acc_cap_len; int rc; /* MS network capability 10.5.5.12 */ msnc_len = *cur++; cur += msnc_len; /* aTTACH Type 10.5.5.2 */ cur++; /* DRX parameter 10.5.5.6 */ cur += 2; /* Mobile Identity (P-TMSI or IMSI) 10.5.1.4 */ mi_len = *cur++; mi_data = cur; cur += mi_len; if (cur >= end) return -ENOSPC; rc = osmo_mobile_identity_decode(mi, mi_data, mi_len, allow_hex); if (rc) return rc; /* Old routing area identification 10.5.5.15. */ rc = osmo_routing_area_id_decode(old_ra, cur, end - cur); if (rc < 0) return rc; cur += rc; /* MS Radio Access Capability 10.5.5.12a */ ms_ra_acc_cap_len = *cur++; cur += ms_ra_acc_cap_len; if (cur > end) return -ENOSPC; decode_gmm_tlv(mi, old_ra, nri, cur, end - cur, allow_hex); return 0; } static int mobile_identity_decode_from_gmm_rau_req(struct osmo_mobile_identity *mi, struct osmo_routing_area_id *old_ra, int *nri, const uint8_t *l3_data, size_t l3_len, bool allow_hex) { const struct gsm48_hdr *gh = (void *)l3_data; const uint8_t *cur = gh->data; const uint8_t *end = l3_data + l3_len; uint8_t ms_ra_acc_cap_len; int rc; /* Update Type 10.5.5.18 */ cur++; if (cur >= end) return -ENOSPC; /* Old routing area identification 10.5.5.15 */ rc = osmo_routing_area_id_decode(old_ra, cur, end - cur); if (rc < 0) return rc; cur += rc; if (cur >= end) return -ENOSPC; /* MS Radio Access Capability 10.5.5.12a */ ms_ra_acc_cap_len = *cur++; cur += ms_ra_acc_cap_len; if (cur > end) return -ENOSPC; decode_gmm_tlv(mi, old_ra, nri, cur, end - cur, allow_hex); return 0; } static int peek_l3_ul_nas(struct hnbgw_context_map *map, const uint8_t *nas_pdu, size_t len, const struct osmo_plmn_id *local_plmn) { const struct gsm48_hdr *gh; int8_t pdisc; uint8_t mtype; const struct gsm48_loc_upd_req *lu; struct gsm48_service_request *cm; struct osmo_location_area_id old_lai; struct osmo_routing_area_id old_ra = {}; int nri = -1; map->l3 = (struct hnbgw_l3_peek){ .gmm_nri_container = -1, }; /* Get the mobile identity from CS MM -- the PS GMM happens further down. * This will return an error for GMM messages, ignore that. */ if (!map->is_ps) osmo_mobile_identity_decode_from_l3_buf(&map->l3.mi, nas_pdu, len, false); /* Get is_emerg and from_other_plmn */ if (len < sizeof(*gh)) { LOGP(DCN, LOGL_ERROR, "Layer 3 message too short for header\n"); return -EINVAL; } gh = (void *)nas_pdu; pdisc = gsm48_hdr_pdisc(gh); mtype = gsm48_hdr_msg_type(gh); map->l3.gsm48_pdisc = pdisc; map->l3.gsm48_msg_type = mtype; /* Determine from_other_plmn and is_emerg */ switch (pdisc) { case GSM48_PDISC_MM: switch (mtype) { case GSM48_MT_MM_LOC_UPD_REQUEST: if (len < sizeof(*gh) + sizeof(*lu)) { LOGP(DCN, LOGL_ERROR, "LU Req message too short\n"); break; } lu = (struct gsm48_loc_upd_req *)gh->data; gsm48_decode_lai2(&lu->lai, &old_lai); map->l3.from_other_plmn = (osmo_plmn_cmp(&old_lai.plmn, local_plmn) != 0); if (map->l3.from_other_plmn) LOGP(DRUA, LOGL_INFO, "LU from other PLMN: old LAI=%s my PLMN=%s\n", osmo_plmn_name_c(OTC_SELECT, &old_lai.plmn), osmo_plmn_name_c(OTC_SELECT, local_plmn)); break; case GSM48_MT_MM_CM_SERV_REQ: if (len < sizeof(*gh) + sizeof(*cm)) { LOGP(DRUA, LOGL_ERROR, "CM Service Req message too short\n"); break; } cm = (struct gsm48_service_request *)&gh->data[0]; map->l3.is_emerg = (cm->cm_service_type == GSM48_CMSERV_EMERGENCY); LOGP(DRUA, LOGL_DEBUG, "CM Service is_emerg=%d\n", map->l3.is_emerg); break; default: break; } break; case GSM48_PDISC_MM_GPRS: switch (mtype) { case GSM48_MT_GMM_ATTACH_REQ: mobile_identity_decode_from_gmm_att_req(&map->l3.mi, &old_ra, &nri, nas_pdu, len, false); LOGP(DRUA, LOGL_DEBUG, "GMM Attach Req mi=%s old_ra=%s nri:%d=0x%x\n", osmo_mobile_identity_to_str_c(OTC_SELECT, &map->l3.mi), osmo_rai_name2_c(OTC_SELECT, &old_ra), nri, nri); if (old_ra.lac.plmn.mcc && osmo_plmn_cmp(&old_ra.lac.plmn, local_plmn)) { map->l3.from_other_plmn = true; LOGP(DRUA, LOGL_INFO, "GMM Attach Req from other PLMN: old RAI=%s my PLMN=%s\n", osmo_rai_name2_c(OTC_SELECT, &old_ra), osmo_plmn_name_c(OTC_SELECT, local_plmn)); } if (nri >= 0) map->l3.gmm_nri_container = nri; break; case GSM48_MT_GMM_RA_UPD_REQ: mobile_identity_decode_from_gmm_rau_req(&map->l3.mi, &old_ra, &nri, nas_pdu, len, false); LOGP(DRUA, LOGL_DEBUG, "GMM Routing Area Upd Req mi=%s old_ra=%s nri:%d=0x%x\n", osmo_mobile_identity_to_str_c(OTC_SELECT, &map->l3.mi), osmo_rai_name2_c(OTC_SELECT, &old_ra), nri, nri); if (old_ra.lac.plmn.mcc && osmo_plmn_cmp(&old_ra.lac.plmn, local_plmn)) { map->l3.from_other_plmn = true; LOGP(DRUA, LOGL_INFO, "GMM Routing Area Upd Req from other PLMN: old RAI=%s my PLMN=%s\n", osmo_rai_name2_c(OTC_SELECT, &old_ra), osmo_plmn_name_c(OTC_SELECT, local_plmn)); } if (nri >= 0) map->l3.gmm_nri_container = nri; break; } break; default: break; } return 0; } static int peek_l3_ul_initial_ue(struct hnbgw_context_map *map, const RANAP_InitialUE_MessageIEs_t *ies) { struct osmo_plmn_id local_plmn; if (g_hnbgw->config.plmn.mcc) { /* The user has configured a PLMN */ local_plmn = g_hnbgw->config.plmn; } else { /* The user has not configured a PLMN, guess from the InitialUE message's LAI IE's PLMN */ if (ies->lai.pLMNidentity.size < 3) { LOGP(DCN, LOGL_ERROR, "Missing PLMN in RANAP InitialUE message\n"); return -EINVAL; } osmo_plmn_from_bcd(ies->lai.pLMNidentity.buf, &local_plmn); } return peek_l3_ul_nas(map, ies->nas_pdu.buf, ies->nas_pdu.size, &local_plmn); } /* Extract a Layer 3 message (NAS PDU) from the uplink RANAP message, and put the info obtained in map->l3. * This is relevant for CN pooling, to decide which CN link to map the RUA context to. */ int hnbgw_peek_l3_ul(struct hnbgw_context_map *map, struct msgb *ranap_msg) { ranap_message *message = hnbgw_decode_ranap_co(ranap_msg); if (!message) { LOGP(DCN, LOGL_ERROR, "Failed to decode RANAP PDU\n"); return -EINVAL; } switch (message->procedureCode) { case RANAP_ProcedureCode_id_InitialUE_Message: return peek_l3_ul_initial_ue(map, &message->msg.initialUE_MessageIEs); default: LOGP(DCN, LOGL_ERROR, "unexpected RANAP PDU in RUA Connect message: %s\n", get_value_string(ranap_procedure_code_vals, message->procedureCode)); return -ENOTSUP; } }