libosmo-gprs/src/gmm/gmm.c

506 lines
14 KiB
C
Raw Normal View History

/* GPRS GMM 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/gprs/gmm/gmm.h>
#include <osmocom/gprs/gmm/gmm_prim.h>
#include <osmocom/gprs/gmm/gmm_private.h>
#include <osmocom/gprs/gmm/gmm_ms_fsm.h>
#include <osmocom/gprs/gmm/gmm_pdu.h>
struct gprs_gmm_ctx *g_ctx;
/* Section 11.2.2 / Table 11.3 GPRS Mobility management timers - MS side */
#define GSM0408_T3310_SECS 15
#define GSM0408_T3311_SECS 15
#define GSM0408_T3316_SECS 30
#define GSM0408_T3318_SECS 20
#define GSM0408_T3320_SECS 15
#define GSM0408_T3321_SECS 15
#define GSM0408_T3330_SECS 15
#define GSM0408_T3340_SECS 10
/* Section 11.2.2 / Table 11.3a GPRS Mobility management timers MS side */
#define GSM0408_T3302_SECS (12 * 60)
#define GSM0408_T3312_SECS (54 * 60)
#define GSM0408_T3314_SECS 44
#define GSM0408_T3317_SECS 15
#define GSM0408_T3319_SECS 30
#define GSM0408_T3323_SECS GSM0408_T3312_SECS /* NOTE 6 */
#define GSM0408_T3325_SECS 60
#define GSM0408_T3346_SECS (15*60)
/* TS 24.008 */
static struct osmo_tdef T_defs_gmm[] = {
{ .T=3302, .default_val=GSM0408_T3302_SECS, .desc = "" },
{ .T=3310, .default_val=GSM0408_T3310_SECS, .desc = "" },
{ .T=3311, .default_val=GSM0408_T3311_SECS, .desc = "" },
{ .T=3312, .default_val=GSM0408_T3312_SECS, .desc = "" },
{ .T=3314, .default_val=GSM0408_T3314_SECS, .desc="READY timer. Forced to STANDBY on expiry timer (s)" },
{ .T=3316, .default_val=GSM0408_T3316_SECS, .desc = "" },
{ .T=3317, .default_val=GSM0408_T3317_SECS, .desc = "" },
{ .T=3318, .default_val=GSM0408_T3318_SECS, .desc = "" },
{ .T=3319, .default_val=GSM0408_T3319_SECS, .desc = "" },
{ .T=3320, .default_val=GSM0408_T3320_SECS, .desc = "" },
{ .T=3321, .default_val=GSM0408_T3321_SECS, .desc = "" },
{ .T=3323, .default_val=GSM0408_T3323_SECS, .desc = "" },
{ .T=3324, .default_val=0 /* provided by the network */, .desc = "" },
{ .T=3325, .default_val=GSM0408_T3323_SECS, .desc = "" },
{ .T=3330, .default_val=GSM0408_T3330_SECS, .desc = "" },
{ .T=3340, .default_val=GSM0408_T3340_SECS, .desc = "" },
{ .T=3346, .default_val=GSM0408_T3346_SECS /* updated by netowrk */, .desc = "" },
{ 0 } /* empty item at the end */
};
int osmo_gprs_gmm_init(enum osmo_gprs_gmm_location location)
{
bool first_init = true;
int rc;
OSMO_ASSERT(location == OSMO_GPRS_GMM_LOCATION_MS || location == OSMO_GPRS_GMM_LOCATION_NETWORK)
if (g_ctx) {
first_init = false;
talloc_free(g_ctx);
}
g_ctx = talloc_zero(NULL, struct gprs_gmm_ctx);
g_ctx->location = location;
g_ctx->T_defs = T_defs_gmm;
INIT_LLIST_HEAD(&g_ctx->gmme_list);
osmo_tdefs_reset(g_ctx->T_defs);
if (first_init) {
rc = gprs_gmm_ms_fsm_init();
if (rc != 0) {
TALLOC_FREE(g_ctx);
return rc;
}
}
return 0;
}
/* Whether GPRS is enabled on the phone.
* See transition GMM-NULL <-> GMM-* in
* "Figure 4.1b/3GPP TS 24.008:GMM main states in the MS" */
void osmo_gprs_gmm_enable_gprs(bool enable_gprs)
{
struct gprs_gmm_entity *gmme;
int ev;
if (g_ctx->gprs_enabled == enable_gprs)
return;
g_ctx->gprs_enabled = enable_gprs;
/* Inform all existing MS: */
ev = enable_gprs ? GPRS_GMM_MS_EV_ENABLE_GPRS_MODE :
GPRS_GMM_MS_EV_DISABLE_GPRS_MODE;
llist_for_each_entry(gmme, &g_ctx->gmme_list, list)
osmo_fsm_inst_dispatch(gmme->ms_fsm.fi, ev, NULL);
}
struct gprs_gmm_entity *gprs_gmm_gmme_alloc(void)
{
struct gprs_gmm_entity *gmme;
gmme = talloc_zero(g_ctx, struct gprs_gmm_entity);
if (!gmme)
return NULL;
if (gprs_gmm_ms_fsm_ctx_init(&gmme->ms_fsm, gmme) < 0) {
talloc_free(gmme);
return NULL;
}
llist_add(&gmme->list, &g_ctx->gmme_list);
return gmme;
}
void gprs_gmm_gmme_free(struct gprs_gmm_entity *gmme)
{
if (!gmme)
return;
LOGGMME(gmme, LOGL_DEBUG, "free()\n");
llist_del(&gmme->list);
talloc_free(gmme);
}
struct gprs_gmm_entity *gprs_gmm_find_gmme_by_tlli(uint32_t tlli)
{
struct gprs_gmm_entity *gmme;
llist_for_each_entry(gmme, &g_ctx->gmme_list, list) {
if (gmme->ptmsi == tlli || gmme->old_ptmsi == tlli)
return gmme;
}
return NULL;
}
static int gprs_gmm_submit_gmmreg_attach_cnf(struct gprs_gmm_entity *gmme, bool accepted, uint8_t cause)
{
struct osmo_gprs_gmm_prim *gmm_prim_tx;
int rc;
gmm_prim_tx = gprs_gmm_prim_alloc_gmmreg_attach_cnf();
gmm_prim_tx->gmmreg.attach_cnf.accepted = accepted;
if (!accepted)
gmm_prim_tx->gmmreg.attach_cnf.rej.cause = cause;
rc = gprs_gmm_prim_call_up_cb(gmm_prim_tx);
return rc;
}
static int gprs_gmm_submit_gmmrr_assing_req(struct gprs_gmm_entity *gmme)
{
struct osmo_gprs_gmm_prim *gmm_prim_tx;
int rc;
gmm_prim_tx = gprs_gmm_prim_alloc_gmmrr_assign_req(gmme->ptmsi);
rc = gprs_gmm_prim_call_down_cb(gmm_prim_tx);
return rc;
}
static int gprs_gmm_submit_llgmm_assing_req(struct gprs_gmm_entity *gmme)
{
struct osmo_gprs_llc_prim *llc_prim_tx;
int rc;
llc_prim_tx = osmo_gprs_llc_prim_alloc_llgm_assign_req(gmme->old_ptmsi);
llc_prim_tx->llgmm.assign_req.tlli_new = gmme->ptmsi;
llc_prim_tx->llgmm.assign_req.gea = gmme->gea;
memcpy(llc_prim_tx->llgmm.assign_req.kc, gmme->kc, ARRAY_SIZE(gmme->kc));
rc = gprs_gmm_prim_call_llc_down_cb(llc_prim_tx);
return rc;
}
/* Tx Identity Response, 9.2.11 */
static int gprs_gmm_tx_id_resp(struct gprs_gmm_entity *gmme,
uint8_t mi_type)
{
struct osmo_gprs_llc_prim *llc_prim;
int rc;
struct msgb *msg;
LOGGMME(gmme, LOGL_INFO, "Tx GMM IDENTITY RESPONSE\n");
llc_prim = osmo_gprs_llc_prim_alloc_ll_unitdata_req(
gmme->ptmsi, OSMO_GPRS_LLC_SAPI_GMM, NULL, GPRS_GMM_ALLOC_SIZE);
msg = llc_prim->oph.msg;
msg->l3h = msg->tail;
rc = gprs_gmm_build_identity_resp(gmme, mi_type, msg);
if (rc < 0) {
msgb_free(msg);
return -EBADMSG;
}
llc_prim->ll.l3_pdu = msg->l3h;
llc_prim->ll.l3_pdu_len = msgb_l3len(msg);
/* TODO:
llc_prim->ll.qos_params[3];
llc_prim->ll.radio_prio;
llc_prim->ll.apply_gea;
llc_prim->ll.apply_gia;
*/
rc = gprs_gmm_prim_call_llc_down_cb(llc_prim);
if (rc < 0)
return rc;
return rc;
}
/* Tx GMM Authentication and ciphering response, 9.4.10 */
static int gprs_gmm_tx_ciph_auth_resp(struct gprs_gmm_entity *gmme, bool imeisv_requested, uint8_t ac_ref_nr, const uint8_t sres[4])
{
struct osmo_gprs_llc_prim *llc_prim;
int rc;
struct msgb *msg;
LOGGMME(gmme, LOGL_INFO, "Tx GMM GMM AUTHENTICATION AND CIPHERING RESPONSE\n");
llc_prim = osmo_gprs_llc_prim_alloc_ll_unitdata_req(
gmme->ptmsi, OSMO_GPRS_LLC_SAPI_GMM, NULL, GPRS_GMM_ALLOC_SIZE);
msg = llc_prim->oph.msg;
msg->l3h = msg->tail;
rc = gprs_gmm_build_ciph_auth_resp(gmme, imeisv_requested, ac_ref_nr, sres, msg);
if (rc < 0) {
msgb_free(msg);
return -EBADMSG;
}
llc_prim->ll.l3_pdu = msg->l3h;
llc_prim->ll.l3_pdu_len = msgb_l3len(msg);
/* TODO:
llc_prim->ll.qos_params[3];
llc_prim->ll.radio_prio;
llc_prim->ll.apply_gea;
llc_prim->ll.apply_gia;
*/
rc = gprs_gmm_prim_call_llc_down_cb(llc_prim);
if (rc < 0)
return rc;
return rc;
}
/* Tx GMM Atach Request, 9.4.1 */
int gprs_gmm_tx_att_req(struct gprs_gmm_entity *gmme,
enum osmo_gprs_gmm_attach_type attach_type,
bool attach_with_imsi)
{
struct osmo_gprs_llc_prim *llc_prim;
int rc;
struct msgb *msg;
LOGGMME(gmme, LOGL_INFO, "Tx GMM ATTACH REQUEST (new P-TMSI=0x%08x)\n", gmme->ptmsi);
llc_prim = osmo_gprs_llc_prim_alloc_ll_unitdata_req(
gmme->ptmsi, OSMO_GPRS_LLC_SAPI_GMM, NULL, GPRS_GMM_ALLOC_SIZE);
msg = llc_prim->oph.msg;
msg->l3h = msg->tail;
rc = gprs_gmm_build_attach_req(gmme,
attach_type,
attach_with_imsi,
msg);
if (rc < 0) {
msgb_free(msg);
return -EBADMSG;
}
llc_prim->ll.l3_pdu = msg->l3h;
llc_prim->ll.l3_pdu_len = msgb_l3len(msg);
/* TODO:
llc_prim->ll.qos_params[3];
llc_prim->ll.radio_prio;
llc_prim->ll.apply_gea;
llc_prim->ll.apply_gia;
*/
rc = gprs_gmm_prim_call_llc_down_cb(llc_prim);
return rc;
}
/* Tx GMM Atach Complete, 9.4.3 */
static int gprs_gmm_tx_att_compl(struct gprs_gmm_entity *gmme)
{
struct osmo_gprs_llc_prim *llc_prim;
int rc;
struct msgb *msg;
LOGGMME(gmme, LOGL_INFO, "Tx GMM ATTACH COMPL\n");
llc_prim = osmo_gprs_llc_prim_alloc_ll_unitdata_req(
gmme->ptmsi, OSMO_GPRS_LLC_SAPI_GMM, NULL, GPRS_GMM_ALLOC_SIZE);
msg = llc_prim->oph.msg;
msg->l3h = msg->tail;
rc = gprs_gmm_build_attach_compl(gmme, msg);
if (rc < 0) {
msgb_free(msg);
return -EBADMSG;
}
llc_prim->ll.l3_pdu = msg->l3h;
llc_prim->ll.l3_pdu_len = msgb_l3len(msg);
/* TODO:
llc_prim->ll.qos_params[3];
llc_prim->ll.radio_prio;
llc_prim->ll.apply_gea;
llc_prim->ll.apply_gia;
*/
rc = gprs_gmm_prim_call_llc_down_cb(llc_prim);
if (rc < 0)
return rc;
return rc;
}
static int gprs_gmm_rx_att_ack(struct gprs_gmm_entity *gmme, struct gsm48_hdr *gh, unsigned int len)
{
struct gsm48_attach_ack *aa;
struct tlv_parsed tp;
int rc;
if (len < sizeof(*gh) + sizeof(*aa)) {
LOGGMME(gmme, LOGL_ERROR, "Rx GMM ATTACH ACCEPT with wrong size %u\n", len);
goto rejected;
}
LOGGMME(gmme, LOGL_DEBUG, "Rx GMM ATTACH ACCEPT\n");
aa = (struct gsm48_attach_ack *)&gh->data[0];
gmme->ra_upd_timer = aa->ra_upd_timer;
gmme->radio_prio = aa->radio_prio;
gsm48_parse_ra(&gmme->ra, (const uint8_t *)&aa->ra_id);
if (len > sizeof(*gh) + sizeof(*aa)) {
rc = gprs_gmm_tlv_parse(&tp, &aa->data[0],
len - (sizeof(*gh) + sizeof(*aa)));
if (rc < 0) {
LOGGMME(gmme, LOGL_ERROR, "Rx GMM ATTACH ACCEPT: failed to parse TLVs %d\n", rc);
goto rejected;
}
if (TLVP_PRESENT(&tp, GSM48_IE_GMM_ALLOC_PTMSI)) {
struct osmo_mobile_identity mi;
if (osmo_mobile_identity_decode(&mi, TLVP_VAL(&tp, GSM48_IE_GMM_ALLOC_PTMSI),
TLVP_LEN(&tp, GSM48_IE_GMM_ALLOC_PTMSI), false)
|| mi.type != GSM_MI_TYPE_TMSI) {
LOGGMME(gmme, LOGL_ERROR, "Cannot decode P-TMSI\n");
goto rejected;
}
gmme->old_ptmsi = gmme->ptmsi;
gmme->ptmsi = mi.tmsi;
}
}
/* Submit GMMREG-ATTACH-CNF as per TS 24.007 Annex C.1 */
rc = gprs_gmm_submit_gmmreg_attach_cnf(gmme, true, 0);
if (rc < 0)
goto rejected;
/* Submit LLGMM-ASSIGN-REQ as per TS 24.007 Annex C.1 */
rc = gprs_gmm_submit_llgmm_assing_req(gmme);
if (rc < 0)
goto rejected;
/* Submit GMMRR-ASSIGN-REQ as per TS 24.007 Annex C.1 */
rc = gprs_gmm_submit_gmmrr_assing_req(gmme);
if (rc < 0)
goto rejected;
rc = gprs_gmm_tx_att_compl(gmme);
if (rc < 0)
goto rejected;
rc = osmo_fsm_inst_dispatch(gmme->ms_fsm.fi, GPRS_GMM_MS_EV_ATTACH_ACCEPTED, NULL);
return rc;
rejected:
return -EINVAL; /* TODO: what to do on error? */
}
static int gprs_gmm_rx_att_rej(struct gprs_gmm_entity *gmme, struct gsm48_hdr *gh, unsigned int len)
{
LOGGMME(gmme, LOGL_ERROR, "Rx GMM ATTACH REJECT not implemented!\n");
return 0; /* TODO */
}
/* Rx GMM Identity Request, 9.2.10 */
static int gprs_gmm_rx_id_req(struct gprs_gmm_entity *gmme, struct gsm48_hdr *gh, unsigned int len)
{
/* 4.7.8.2: "An MS shall be ready to respond to an IDENTITY REQUEST message at any time."
* "Upon receipt of the IDENTITY REQUEST message the MS sends back an IDENTITY RESPONSE message.
* The IDENTITY RESPONSE message shall contain the identification parameters as requested by the network"
*/
uint8_t id_type;
if (len < sizeof(struct gsm48_hdr) + 1) {
LOGGMME(gmme, LOGL_ERROR, "Rx GMM IDENTITY REQUEST with wrong size %u\n", len);
return -EINVAL;
}
id_type = gh->data[0] & 0xf;
LOGGMME(gmme, LOGL_DEBUG, "Rx GMM IDENTITY REQUEST mi_type=%s\n",
gsm48_mi_type_name(id_type));
return gprs_gmm_tx_id_resp(gmme, id_type);
}
/* Rx GMM Authentication and ciphering request, 9.4.9 */
static int gprs_gmm_rx_auth_ciph_req(struct gprs_gmm_entity *gmme, struct gsm48_hdr *gh, unsigned int len)
{
struct gsm48_auth_ciph_req *acreq;
struct tlv_parsed tp;
int rc;
bool imeisv_requested = false;
uint8_t sres[4] = {};
if (len < sizeof(*gh) + sizeof(*acreq)) {
LOGGMME(gmme, LOGL_ERROR, "Rx GMM AUTHENTICATION AND CIPHERING REQUEST with wrong size %u\n", len);
return -EINVAL;
}
LOGGMME(gmme, LOGL_DEBUG, "Rx GMM AUTHENTICATION AND CIPHERING REQUEST\n");
acreq = (struct gsm48_auth_ciph_req *)&gh->data[0];
if (len > sizeof(*gh) + sizeof(*acreq)) {
rc = gprs_gmm_tlv_parse(&tp, &acreq->data[0],
len - (sizeof(*gh) + sizeof(*acreq)));
if (rc < 0) {
LOGGMME(gmme, LOGL_ERROR, "Rx GMM AUTHENTICATION AND CIPHERING REQUEST: failed to parse TLVs %d\n", rc);
return -EINVAL;
}
if (TLVP_PRESENT(&tp, GSM48_IE_GMM_IMEISV))
imeisv_requested = !!*((uint8_t *)TLVP_VAL(&tp, GSM48_IE_GMM_IMEISV));
}
rc = gprs_gmm_tx_ciph_auth_resp(gmme, imeisv_requested, acreq->ac_ref_nr, sres);
if (rc < 0)
return rc;
/* Submit LLGMM-ASSIGN-REQ as per TS 24.007 Annex C.1 */
rc = gprs_gmm_submit_llgmm_assing_req(gmme);
return rc;
}
/* Rx GPRS Mobility Management. */
int gprs_gmm_rx(struct gprs_gmm_entity *gmme, struct gsm48_hdr *gh, unsigned int len)
{
int rc = 0;
if (len < sizeof(struct gsm48_hdr)) {
LOGGMME(gmme, LOGL_ERROR, "Rx GMM message too short! len=%u\n", len);
return -EINVAL;
}
switch (gh->msg_type) {
case GSM48_MT_GMM_ATTACH_ACK:
rc = gprs_gmm_rx_att_ack(gmme, gh, len);
break;
case GSM48_MT_GMM_ATTACH_REJ:
rc = gprs_gmm_rx_att_rej(gmme, gh, len);
break;
case GSM48_MT_GMM_ID_REQ:
rc = gprs_gmm_rx_id_req(gmme, gh, len);
break;
case GSM48_MT_GMM_AUTH_CIPH_REQ:
rc = gprs_gmm_rx_auth_ciph_req(gmme, gh, len);
break;
default:
LOGGMME(gmme, LOGL_ERROR,
"Rx GMM message not implemented! type=%u len=%u\n",
gh->msg_type, len);
rc = -EINVAL;
}
return rc;
}