430 lines
11 KiB
C
430 lines
11 KiB
C
/* GPRS SM as per 3GPP TS 24.008, TS 24.007 */
|
|
/*
|
|
* (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
|
|
*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include <stdint.h>
|
|
#include <errno.h>
|
|
#include <arpa/inet.h>
|
|
|
|
#include <osmocom/core/talloc.h>
|
|
#include <osmocom/core/tdef.h>
|
|
#include <osmocom/gsm/protocol/gsm_04_08_gprs.h>
|
|
#include <osmocom/gsm/gsm48.h>
|
|
|
|
#include <osmocom/gprs/sndcp/sndcp_prim.h>
|
|
|
|
#include <osmocom/gprs/sm/sm.h>
|
|
#include <osmocom/gprs/sm/sm_prim.h>
|
|
#include <osmocom/gprs/sm/sm_private.h>
|
|
#include <osmocom/gprs/sm/sm_ms_fsm.h>
|
|
#include <osmocom/gprs/sm/sm_pdu.h>
|
|
|
|
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;
|
|
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);
|
|
|
|
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_tlli = sme->ms->gmm.tlli;
|
|
} 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_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;
|
|
}
|
|
|
|
/* 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;
|
|
}
|