neels has uploaded this change for review.
cnpool: extract Mobile Identity from RANAP payload
For InitialUE messages, decode the RANAP and NAS PDU to obtain the
Mobile Identity. Store it in map->l3.
A following patch will use this mobile identity to decide on which CN
links to pick from the CN pools for this subscriber.
Add the new information to LOG_MAP() as ubiquitous logging context.
Related: SYS#6412
Change-Id: I373d665c9684b607207f68094188eab63209db51
---
M include/osmocom/hnbgw/context_map.h
M include/osmocom/hnbgw/hnbgw.h
M src/osmo-hnbgw/Makefile.am
A src/osmo-hnbgw/hnbgw_l3.c
M src/osmo-hnbgw/hnbgw_rua.c
5 files changed, 353 insertions(+), 3 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/osmo-hnbgw refs/changes/33/33133/1
diff --git a/include/osmocom/hnbgw/context_map.h b/include/osmocom/hnbgw/context_map.h
index bcc3238..2920438 100644
--- a/include/osmocom/hnbgw/context_map.h
+++ b/include/osmocom/hnbgw/context_map.h
@@ -3,12 +3,19 @@
#include <stdint.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/hnbgw/hnbgw.h>
+#include <osmocom/gsm/gsm48.h>
#define LOG_MAP(HNB_CTX_MAP, SUBSYS, LEVEL, FMT, ARGS...) \
LOGHNB((HNB_CTX_MAP) ? (HNB_CTX_MAP)->hnb_ctx : NULL, \
- SUBSYS, LEVEL, "RUA-%u %s: " FMT, \
+ SUBSYS, LEVEL, "RUA-%u %s MI=%s%s%s: " FMT, \
(HNB_CTX_MAP) ? (HNB_CTX_MAP)->rua_ctx_id : 0, \
- (HNB_CTX_MAP) ? ((HNB_CTX_MAP)->is_ps ? "PS" : "CS") : "NULL", \
+ (HNB_CTX_MAP) ? \
+ ( (HNB_CTX_MAP)->cnlink ? (HNB_CTX_MAP)->cnlink->name \
+ : ((HNB_CTX_MAP)->is_ps ? "PS" : "CS") ) \
+ : "NULL", \
+ (HNB_CTX_MAP) ? osmo_mobile_identity_to_str_c(OTC_SELECT, &(HNB_CTX_MAP)->l3.mi) : "null", \
+ (HNB_CTX_MAP) && (HNB_CTX_MAP)->l3.from_other_plmn ? " (from other PLMN)" : "", \
+ (HNB_CTX_MAP) && (HNB_CTX_MAP)->l3.is_emerg ? " EMERGENCY" : "", \
##ARGS)
/* All these events' data argument may either be NULL, or point to a RANAP msgb.
@@ -75,6 +82,21 @@
struct hnb_context;
struct hnbgw_cnlink;
+struct hnbgw_l3_peek {
+ /* L3 message type, like GSM48_PDISC_MM+GSM48_MT_MM_LOC_UPD_REQUEST... / GSM48_PDISC_MM_GPRS+GSM48_MT_GMM_ATTACH_REQ... */
+ uint8_t gsm48_pdisc;
+ uint8_t gsm48_msg_type;
+ /* The Mobile Identity from MM and GMM messages */
+ struct osmo_mobile_identity mi;
+ /* On PS, the "TMSI Based NRI Container", 10 bit integer, or -1 if not present.
+ * This is only for PS -- for CS, the NRI is in the TMSI obtained from 'mi' above. */
+ int gmm_nri_container;
+ /* For a CM Service Request for voice call, true if this is for an Emergency Call, false otherwise. */
+ bool is_emerg;
+ /* True if the NAS PDU indicates that the UE was previously attached to a different PLMN than the local PLMN. */
+ bool from_other_plmn;
+};
+
struct hnbgw_context_map {
/* entry in the per-CN list of mappings */
struct llist_head hnbgw_cnlink_entry;
@@ -103,6 +125,9 @@
/* False for CS, true for PS */
bool is_ps;
+ /* Information extracted from RUA Connect's RANAP InitialUE message */
+ struct hnbgw_l3_peek l3;
+
/* When an FSM is asked to disconnect but must still wait for a response, it may set this flag, to continue to
* disconnect once the response is in. In particular, when SCCP is asked to disconnect after an SCCP Connection
* Request was already sent and while waiting for a Connection Confirmed, we should still wait for the SCCP CC
diff --git a/include/osmocom/hnbgw/hnbgw.h b/include/osmocom/hnbgw/hnbgw.h
index cc32f93..8183cbc 100644
--- a/include/osmocom/hnbgw/hnbgw.h
+++ b/include/osmocom/hnbgw/hnbgw.h
@@ -312,3 +312,5 @@
}
struct msgb *hnbgw_ranap_msg_alloc(const char *name);
+
+int hnbgw_peek_l3(struct hnbgw_context_map *map, struct msgb *ranap_msg);
diff --git a/src/osmo-hnbgw/Makefile.am b/src/osmo-hnbgw/Makefile.am
index e8b4966..5fbdf74 100644
--- a/src/osmo-hnbgw/Makefile.am
+++ b/src/osmo-hnbgw/Makefile.am
@@ -33,6 +33,7 @@
libhnbgw_la_SOURCES = \
hnbgw.c \
hnbgw_hnbap.c \
+ hnbgw_l3.c \
hnbgw_rua.c \
hnbgw_ranap.c \
hnbgw_vty.c \
diff --git a/src/osmo-hnbgw/hnbgw_l3.c b/src/osmo-hnbgw/hnbgw_l3.c
new file mode 100644
index 0000000..97d958e
--- /dev/null
+++ b/src/osmo-hnbgw/hnbgw_l3.c
@@ -0,0 +1,296 @@
+/* OsmoHNBGW implementation of CS and PS Level3 message decoding (NAS PDU */
+
+/* Copyright 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include "asn1helpers.h"
+
+#include <osmocom/gsm/gsm48.h>
+#include <osmocom/gsm/protocol/gsm_04_08_gprs.h>
+
+#include <osmocom/hnbgw/hnbgw.h>
+#include <osmocom/hnbgw/hnbgw_rua.h>
+#include <osmocom/hnbgw/context_map.h>
+#include <osmocom/ranap/ranap_ies_defs.h>
+
+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 *mi_data;
+ uint8_t mi_len;
+ uint8_t msnc_len;
+ uint8_t ms_ra_acc_cap_len;
+ const struct gsm48_ra_id *ra_id;
+ 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;
+
+ rc = osmo_mobile_identity_decode(mi, mi_data, mi_len, allow_hex);
+ if (rc)
+ return rc;
+
+ /* Old routing area identification 10.5.5.15. */
+ ra_id = (void*)cur;
+ osmo_plmn_from_bcd(ra_id->digits, &old_ra->lac.plmn);
+ old_ra->lac.lac = osmo_load16be(&ra_id->lac);
+ old_ra->rac = ra_id->rac;
+ cur += sizeof(*ra_id);
+
+ /* MS Radio Access Capability 10.5.5.12a */
+ ms_ra_acc_cap_len = *cur++;
+ cur += ms_ra_acc_cap_len;
+
+ decode_gmm_tlv(mi, old_ra, nri, cur, (l3_data + l3_len) - 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;
+ uint8_t ms_ra_acc_cap_len;
+ const struct gsm48_ra_id *ra_id;
+
+ /* Update Type 10.5.5.18 */
+ cur++;
+
+ /* Old routing area identification 10.5.5.15 */
+ ra_id = (void*)cur;
+ osmo_plmn_from_bcd(ra_id->digits, &old_ra->lac.plmn);
+ old_ra->lac.lac = osmo_load16be(&ra_id->lac);
+ old_ra->rac = ra_id->rac;
+ cur += sizeof(*ra_id);
+
+ /* MS Radio Access Capability 10.5.5.12a */
+ ms_ra_acc_cap_len = *cur++;
+ cur += ms_ra_acc_cap_len;
+
+ decode_gmm_tlv(mi, old_ra, nri, cur, (l3_data + l3_len) - cur, allow_hex);
+ return 0;
+}
+
+static int peek_l3_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);
+ LOGP(DCN, LOGL_DEBUG, "LU from_other_plmn=%d\n", map->l3.from_other_plmn);
+ break;
+
+ case GSM48_MT_MM_CM_SERV_REQ:
+ if (len < sizeof(*gh) + sizeof(*cm)) {
+ LOGP(DCN, 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(DCN, 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_initial_ue(struct hnbgw_context_map *map, const RANAP_InitialUE_MessageIEs_t *ies)
+{
+ struct osmo_plmn_id 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, &plmn);
+
+ return peek_l3_nas(map, ies->nas_pdu.buf, ies->nas_pdu.size, &plmn);
+}
+
+/* Extract a Layer 3 message (NAS PDU) from the 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(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_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;
+ }
+}
diff --git a/src/osmo-hnbgw/hnbgw_rua.c b/src/osmo-hnbgw/hnbgw_rua.c
index abda5ea..2a7dc20 100644
--- a/src/osmo-hnbgw/hnbgw_rua.c
+++ b/src/osmo-hnbgw/hnbgw_rua.c
@@ -38,7 +38,9 @@
#include <osmocom/rua/rua_common.h>
#include <osmocom/rua/rua_ies_defs.h>
#include <osmocom/hnbgw/context_map.h>
+#include <osmocom/hnbgw/hnbgw_rua.h>
#include <osmocom/hnbap/HNBAP_CN-DomainIndicator.h>
+#include <osmocom/ranap/ranap_ies_defs.h>
static const char *cn_domain_indicator_to_str(RUA_CN_DomainIndicator_t cN_DomainIndicator)
{
@@ -208,7 +210,13 @@
map = context_map_alloc(hnb, rua_ctx_id, is_ps);
OSMO_ASSERT(map);
- /* FUTURE: extract mobile identity and store in map-> */
+ if (hnbgw_peek_l3(map, ranap_msg)) {
+ LOGP(DCN, LOGL_NOTICE, "Failed to extract Mobile Identity from RUA Connect message's RANAP payload\n");
+ LOGP(DCN, LOGL_DEBUG, "Connect message's RANAP payload: %s\n",
+ osmo_hexdump_nospc_c(OTC_SELECT, ranap_msg->data, ranap_msg->len));
+ /* still continue to select a hopefully adequate link */
+ }
+ /* map->l3 now contains all the interesting information from the NAS PDU */
cnlink = hnbgw_cnlink_select(map);
if (!cnlink) {
To view, visit change 33133. To unsubscribe, or for help writing mail filters, visit settings.