/* GPRS SM as per 3GPP TS 24.008, TS 24.007 */ /* * (C) 2023 by sysmocom - s.f.m.c. GmbH * * All Rights Reserved * * 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 #include #include #include #include #include #include #include #include #include #include #include #include struct gprs_sm_ctx *g_sm_ctx; /* TS 24.008 */ static struct osmo_tdef T_defs_sm[] = { { .T=3380, .default_val=30, .desc = "" }, { 0 } /* empty item at the end */ }; static void gprs_sm_ctx_free(void) { struct gprs_sm_ms *ms; while ((ms = llist_first_entry_or_null(&g_sm_ctx->ms_list, struct gprs_sm_ms, list))) gprs_sm_ms_free(ms); talloc_free(g_sm_ctx); } int osmo_gprs_sm_init(enum osmo_gprs_sm_location location) { bool first_init = true; int rc; OSMO_ASSERT(location == OSMO_GPRS_SM_LOCATION_MS || location == OSMO_GPRS_SM_LOCATION_NETWORK) if (g_sm_ctx) { first_init = false; gprs_sm_ctx_free(); } g_sm_ctx = talloc_zero(NULL, struct gprs_sm_ctx); g_sm_ctx->location = location; g_sm_ctx->T_defs = T_defs_sm; INIT_LLIST_HEAD(&g_sm_ctx->ms_list); osmo_tdefs_reset(g_sm_ctx->T_defs); if (first_init) { rc = gprs_sm_ms_fsm_init(); if (rc != 0) { TALLOC_FREE(g_sm_ctx); return rc; } } return 0; } struct gprs_sm_ms *gprs_sm_ms_alloc(uint32_t ms_id) { struct gprs_sm_ms *ms; ms = talloc_zero(g_sm_ctx, struct gprs_sm_ms); if (!ms) return NULL; ms->ms_id = ms_id; llist_add(&ms->list, &g_sm_ctx->ms_list); return ms; } void gprs_sm_ms_free(struct gprs_sm_ms *ms) { unsigned int i; if (!ms) return; LOGMS(ms, LOGL_DEBUG, "free()\n"); for (i = 0; i < ARRAY_SIZE(ms->pdp); i++) gprs_sm_entity_free(ms->pdp[i]); llist_del(&ms->list); talloc_free(ms); } struct gprs_sm_ms *gprs_sm_find_ms_by_id(uint32_t ms_id) { struct gprs_sm_ms *ms; llist_for_each_entry(ms, &g_sm_ctx->ms_list, list) { if (ms->ms_id == ms_id) return ms; } return NULL; } struct gprs_sm_ms *gprs_sm_find_ms_by_tlli(uint32_t tlli) { struct gprs_sm_ms *ms; llist_for_each_entry(ms, &g_sm_ctx->ms_list, list) { if (ms->gmm.tlli == tlli) return ms; } return NULL; } struct gprs_sm_entity *gprs_sm_entity_alloc(struct gprs_sm_ms *ms, uint32_t nsapi) { struct gprs_sm_entity *sme; sme = talloc_zero(g_sm_ctx, struct gprs_sm_entity); if (!sme) return NULL; sme->ms = ms; sme->sess_id = g_sm_ctx->next_sess_id++; sme->nsapi = nsapi; if (gprs_sm_ms_fsm_ctx_init(&sme->ms_fsm, sme) < 0) { talloc_free(sme); return NULL; } OSMO_ASSERT(sme->ms->pdp[sme->nsapi] == NULL); sme->ms->pdp[sme->nsapi] = sme; return sme; } void gprs_sm_entity_free(struct gprs_sm_entity *sme) { if (!sme) return; gprs_sm_ms_fsm_ctx_release(&sme->ms_fsm); sme->ms->pdp[sme->nsapi] = NULL; talloc_free(sme); } struct gprs_sm_entity *gprs_sm_find_sme_by_sess_id(uint32_t sess_id) { struct gprs_sm_ms *ms; unsigned int i; llist_for_each_entry(ms, &g_sm_ctx->ms_list, list) { for (i = 0; i < ARRAY_SIZE(ms->pdp); i++) { if (!ms->pdp[i]) continue; if (ms->pdp[i]->sess_id != sess_id) continue; return ms->pdp[i]; } } return NULL; } int gprs_sm_submit_gmmsm_assign_req(const struct gprs_sm_entity *sme) { struct gprs_sm_ms *ms = sme->ms; struct osmo_gprs_gmm_prim *gmm_prim_tx; int rc; gmm_prim_tx = osmo_gprs_gmm_prim_alloc_gmmsm_establish_req(sme->sess_id); gmm_prim_tx->gmmsm.establish_req.attach_type = OSMO_GPRS_GMM_ATTACH_TYPE_GPRS; gmm_prim_tx->gmmsm.establish_req.attach_with_imsi = (ms->gmm.ptmsi == GSM_RESERVED_TMSI); gmm_prim_tx->gmmsm.establish_req.ptmsi = ms->gmm.ptmsi; gmm_prim_tx->gmmsm.establish_req.ptmsi_sig = ms->gmm.ptmsi_sig; OSMO_STRLCPY_ARRAY(gmm_prim_tx->gmmsm.establish_req.imsi, ms->gmm.imsi); OSMO_STRLCPY_ARRAY(gmm_prim_tx->gmmsm.establish_req.imei, ms->gmm.imei); OSMO_STRLCPY_ARRAY(gmm_prim_tx->gmmsm.establish_req.imeisv, ms->gmm.imeisv); memcpy(&gmm_prim_tx->gmmsm.establish_req.old_rai, &ms->gmm.ra, sizeof(ms->gmm.ra)); rc = gprs_sm_prim_call_gmm_down_cb(gmm_prim_tx); return rc; } int gprs_sm_submit_smreg_pdp_act_cnf(const struct gprs_sm_entity *sme, enum gsm48_gsm_cause cause) { struct osmo_gprs_sm_prim *sm_prim_tx; int rc; sm_prim_tx = gprs_sm_prim_alloc_smreg_pdp_act_cnf(); sm_prim_tx->smreg.ms_id = sme->ms->ms_id; sm_prim_tx->smreg.pdp_act_cnf.accepted = (cause == 0); sm_prim_tx->smreg.pdp_act_cnf.nsapi = sme->nsapi; sm_prim_tx->smreg.pdp_act_cnf.pco_len = sme->pco_len; if (sme->pco_len) memcpy(sm_prim_tx->smreg.pdp_act_cnf.pco, &sme->pco, sme->pco_len); if (sm_prim_tx->smreg.pdp_act_cnf.accepted) { sm_prim_tx->smreg.pdp_act_cnf.acc.pdp_addr_ietf_type = sme->pdp_addr_ietf_type; memcpy(&sm_prim_tx->smreg.pdp_act_cnf.acc.pdp_addr_v4, &sme->pdp_addr_v4, sizeof(sme->pdp_addr_v4)); memcpy(&sm_prim_tx->smreg.pdp_act_cnf.acc.pdp_addr_v6, &sme->pdp_addr_v6, sizeof(sme->pdp_addr_v6)); sm_prim_tx->smreg.pdp_act_cnf.acc.radio_prio = sme->radio_prio; sm_prim_tx->smreg.pdp_act_cnf.acc.qos_len = sme->qos_len; if (sme->qos_len) memcpy(sm_prim_tx->smreg.pdp_act_cnf.acc.qos, &sme->qos, sme->qos_len); sm_prim_tx->smreg.pdp_act_cnf.acc.gmm.allocated_ptmsi = sme->ms->gmm.ptmsi; sm_prim_tx->smreg.pdp_act_cnf.acc.gmm.allocated_ptmsi_sig = sme->ms->gmm.ptmsi_sig; sm_prim_tx->smreg.pdp_act_cnf.acc.gmm.allocated_tlli = sme->ms->gmm.tlli; memcpy(&sm_prim_tx->smreg.pdp_act_cnf.acc.gmm.rai, &sme->ms->gmm.ra, sizeof(sme->ms->gmm.ra)); } else { sm_prim_tx->smreg.pdp_act_cnf.rej.cause = cause; } rc = gprs_sm_prim_call_up_cb(sm_prim_tx); return rc; } int gprs_sm_submit_smreg_pdp_deact_ind(const struct gprs_sm_entity *sme, enum gsm48_gsm_cause cause) { struct osmo_gprs_sm_prim *sm_prim_tx; int rc; sm_prim_tx = gprs_sm_prim_alloc_smreg_pdp_deact_ind(); sm_prim_tx->smreg.ms_id = sme->ms->ms_id; sm_prim_tx->smreg.pdp_deact_ind.nsapi[0] = sme->nsapi; sm_prim_tx->smreg.pdp_deact_ind.num_nsapi = 1; sm_prim_tx->smreg.pdp_deact_ind.tear_down_ind = 0; sm_prim_tx->smreg.pdp_deact_ind.cause = cause; sm_prim_tx->smreg.pdp_deact_ind.pco_len = sme->pco_len; if (sme->pco_len) memcpy(sm_prim_tx->smreg.pdp_deact_cnf.pco, &sme->pco, sme->pco_len); rc = gprs_sm_prim_call_up_cb(sm_prim_tx); return rc; } int gprs_sm_submit_snsm_act_ind(const struct gprs_sm_entity *sme) { struct osmo_gprs_sndcp_prim *sndcp_prim_tx; int rc; sndcp_prim_tx = osmo_gprs_sndcp_prim_alloc_snsm_activate_ind( sme->ms->gmm.tlli, sme->nsapi, sme->llc_sapi); //sndcp_prim_tx->snsm.activat_ind.qos_params = ; /* TODO */ //sndcp_prim_tx->snsm.activat_ind.radio_prio = 0; /* TODO */ rc = gprs_sm_prim_call_sndcp_up_cb(sndcp_prim_tx); return rc; } int gprs_sm_submit_snsm_deact_ind(const struct gprs_sm_entity *sme) { struct osmo_gprs_sndcp_prim *sndcp_prim_tx; int rc; sndcp_prim_tx = osmo_gprs_sndcp_prim_alloc_snsm_deactivate_ind( sme->ms->gmm.tlli, sme->nsapi); rc = gprs_sm_prim_call_sndcp_up_cb(sndcp_prim_tx); return rc; } /* Tx SM Activate PDP context request, 9.5.1 */ int gprs_sm_tx_act_pdp_ctx_req(struct gprs_sm_entity *sme) { struct osmo_gprs_gmm_prim *gmm_prim; int rc; struct msgb *msg; LOGSME(sme, LOGL_INFO, "Tx SM Activate PDP Context Request\n"); gmm_prim = osmo_gprs_gmm_prim_alloc_gmmsm_unitdata_req( sme->ms->ms_id, NULL, GPRS_SM_ALLOC_SIZE); msg = gmm_prim->oph.msg; msg->l3h = msg->tail; rc = gprs_sm_build_act_pdp_ctx_req(sme, msg); if (rc < 0) { msgb_free(msg); return -EBADMSG; } gmm_prim->gmmsm.unitdata_req.smpdu = msg->l3h; gmm_prim->gmmsm.unitdata_req.smpdu_len = msgb_l3len(msg); rc = gprs_sm_prim_call_gmm_down_cb(gmm_prim); return rc; } /* 3GPP TS 24.008 § 9.5.2: Activate PDP Context Accept */ static int gprs_sm_rx_act_pdp_ack(struct gprs_sm_entity *sme, struct gsm48_hdr *gh, unsigned int len) { struct tlv_parsed tp; int rc; uint8_t *ofs = (uint8_t *)gh; uint8_t qos_len; ofs += sizeof(*gh); //uint8_t transaction_id = gsm48_hdr_trans_id(gh); LOGSME(sme, LOGL_INFO, "Rx SM Activate PDP Context Accept\n"); if (len < (ofs + 2) - (uint8_t *)gh) goto tooshort; sme->llc_sapi = *ofs++; qos_len = *ofs++; if (qos_len > ARRAY_SIZE(sme->qos)) { LOGSME(sme, LOGL_ERROR, "Rx SM Activate PDP Context Accept: QoS size too big! %u\n", qos_len); goto rejected; } if (len < (ofs + qos_len) - (uint8_t *)gh) goto tooshort; memcpy(sme->qos, ofs, qos_len); ofs += qos_len; if (len < (ofs + 1) - (uint8_t *)gh) goto tooshort; sme->radio_prio = *ofs++; if (len > ofs - (uint8_t *)gh) { rc = gprs_sm_tlv_parse(&tp, ofs, len - (ofs - (uint8_t *)gh)); if (rc < 0) { LOGSME(sme, LOGL_ERROR, "Rx SM Activate PDP Context Accept: failed to parse TLVs %d\n", rc); goto rejected; } if (TLVP_PRESENT(&tp, GSM48_IE_GSM_PDP_ADDR)) { rc = gprs_sm_pdp_addr_dec( (const struct gprs_sm_pdp_addr *)TLVP_VAL(&tp, GSM48_IE_GSM_PDP_ADDR), TLVP_LEN(&tp, GSM48_IE_GSM_PDP_ADDR), &sme->pdp_addr_ietf_type, &sme->pdp_addr_v4, &sme->pdp_addr_v6); if (rc < 0) goto rejected; } if (TLVP_PRESENT(&tp, GSM48_IE_GSM_PROTO_CONF_OPT)) { if (TLVP_LEN(&tp, GSM48_IE_GSM_PROTO_CONF_OPT) > ARRAY_SIZE(sme->pco)) { LOGSME(sme, LOGL_ERROR, "Rx SM Activate PDP Context Accept: PCO size too big! %u\n", TLVP_LEN(&tp, GSM48_IE_GSM_PROTO_CONF_OPT)); goto rejected; } sme->pco_len = TLVP_LEN(&tp, GSM48_IE_GSM_PROTO_CONF_OPT); if (sme->pco_len) memcpy(sme->pco, TLVP_VAL(&tp, GSM48_IE_GSM_PROTO_CONF_OPT), sme->pco_len); } } rc = osmo_fsm_inst_dispatch(sme->ms_fsm.fi, GPRS_SM_MS_EV_RX_ACT_PDP_CTX_ACC, NULL); if (rc < 0) goto rejected; return rc; tooshort: LOGSME(sme, LOGL_ERROR, "Rx GMM message too short! len=%u\n", len); rejected: return -EINVAL; /* TODO: what to do on error? */ } /* 3GPP TS 24.008 § 9.5.3: Activate PDP Context rej */ static int gprs_sm_rx_act_pdp_rej(struct gprs_sm_entity *sme, struct gsm48_hdr *gh, unsigned int len) { struct tlv_parsed tp; int rc; enum gsm48_gsm_cause cause; uint8_t *ofs = (uint8_t *)gh; ofs += sizeof(*gh); //uint8_t transaction_id = gsm48_hdr_trans_id(gh); LOGSME(sme, LOGL_INFO, "Rx SM Activate PDP Context Reject\n"); if (len < (ofs + 1) - (uint8_t *)gh) goto tooshort; cause = *ofs++; if (len > ofs - (uint8_t *)gh) { rc = gprs_sm_tlv_parse(&tp, ofs, len - (ofs - (uint8_t *)gh)); if (rc < 0) { LOGSME(sme, LOGL_ERROR, "Rx SM Activate PDP Context Reject: failed to parse TLVs %d\n", rc); goto rejected; } if (TLVP_PRESENT(&tp, GSM48_IE_GSM_PROTO_CONF_OPT)) { if (TLVP_LEN(&tp, GSM48_IE_GSM_PROTO_CONF_OPT) > ARRAY_SIZE(sme->pco)) { LOGSME(sme, LOGL_ERROR, "Rx SM Activate PDP Context Reject: PCO size too big! %u\n", TLVP_LEN(&tp, GSM48_IE_GSM_PROTO_CONF_OPT)); goto rejected; } sme->pco_len = TLVP_LEN(&tp, GSM48_IE_GSM_PROTO_CONF_OPT); if (sme->pco_len) memcpy(sme->pco, TLVP_VAL(&tp, GSM48_IE_GSM_PROTO_CONF_OPT), sme->pco_len); } } rc = osmo_fsm_inst_dispatch(sme->ms_fsm.fi, GPRS_SM_MS_EV_RX_ACT_PDP_CTX_REJ, &cause); if (rc < 0) goto rejected; return rc; tooshort: LOGSME(sme, LOGL_ERROR, "Rx GMM message too short! len=%u\n", len); rejected: return -EINVAL; /* TODO: what to do on error? */ } /* Rx Session Management PDU */ int gprs_sm_rx(struct gprs_sm_entity *sme, struct gsm48_hdr *gh, unsigned int len) { int rc = 0; if (len < sizeof(struct gsm48_hdr)) { LOGSME(sme, LOGL_ERROR, "Rx GMM message too short! len=%u\n", len); return -EINVAL; } switch (gh->msg_type) { case GSM48_MT_GSM_ACT_PDP_ACK: rc = gprs_sm_rx_act_pdp_ack(sme, gh, len); break; case GSM48_MT_GSM_ACT_PDP_REJ: rc = gprs_sm_rx_act_pdp_rej(sme, gh, len); break; default: LOGSME(sme, LOGL_ERROR, "Rx SM message not implemented! type=%u len=%u\n", gh->msg_type, len); rc = -EINVAL; } return rc; } void gprs_sm_handle_ie_pdp_ctx_status(struct gprs_sm_ms *ms, const uint8_t *pdp_status) { unsigned int i; /* 24.008 4.7.5.1.3: If the PDP context status information element is * included in ROUTING AREA UPDATE REQUEST message, then the network * shall deactivate all those PDP contexts locally (without peer to * peer signalling between the MS and the network), which are not in SM * state PDP-INACTIVE on network side but are indicated by the MS as * being in state PDP-INACTIVE. */ for (i = 0; i < ARRAY_SIZE(ms->pdp); i++) { struct gprs_sm_entity *sme = ms->pdp[i]; bool inactive; if (!sme) continue; inactive = (sme->nsapi < 8) ? !(pdp_status[0] & (1 << sme->nsapi)) : !(pdp_status[1] & (1 << (sme->nsapi - 8))); if (!inactive) continue; LOGSME(sme, LOGL_NOTICE, "PDP context status informs PDP context is PDP-INACTIVE, deleting\n"); gprs_sm_submit_snsm_deact_ind(sme); /* See TS 24.007 Appendix C.10: Wait for SNSM-DEACTIVATE.rsp, * then submit SMREG-DEACTIVATE.ind and free the object */ } }