osmo-msc/src/libmsc/gsm_09_11.c

557 lines
16 KiB
C

/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
* (C) 2008, 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
* (C) 2009 by Mike Haben <michael.haben@btinternet.com>
* (C) 2018 by Vadim Yanitskiy <axilirator@gmail.com>
*
* 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/>.
*
*/
/**
* MSC-specific handling of call independent Supplementary
* Services messages (NC_SS) according to GSM TS 09.11
* "Signalling interworking for supplementary services".
*/
#include <stdio.h>
#include <errno.h>
#include <stdbool.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/rate_ctr.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/msgb.h>
#include <osmocom/gsm/protocol/gsm_04_80.h>
#include <osmocom/gsm/gsm0480.h>
#include <osmocom/gsm/tlv.h>
#include <osmocom/msc/gsm_04_80.h>
#include <osmocom/msc/gsm_subscriber.h>
#include <osmocom/msc/debug.h>
#include <osmocom/msc/vlr.h>
#include <osmocom/msc/gsm_04_08.h>
#include <osmocom/msc/transaction.h>
#include <osmocom/gsupclient/gsup_client.h>
#include <osmocom/msc/msc_ifaces.h>
/* FIXME: choose a proper range */
static uint32_t new_callref = 0x20000001;
static void ncss_session_timeout_handler(void *_trans)
{
struct gsm_trans *trans = (struct gsm_trans *) _trans;
struct osmo_gsup_message gsup_msg = { 0 };
/* The timeout might be disabled from the VTY */
if (trans->net->ncss_guard_timeout == 0)
return;
LOGP(DMM, LOGL_NOTICE, "SS/USSD session timeout, releasing "
"transaction (trans=%p, callref=%x)\n", trans, trans->callref);
/* Indicate connection release to subscriber (if active) */
if (trans->conn != NULL) {
/* This pair of cause location and value is used by commercial networks */
msc_send_ussd_release_complete_cause(trans->conn, trans->transaction_id,
GSM48_CAUSE_LOC_PUN_S_LU, GSM48_CC_CAUSE_NORMAL_UNSPEC);
}
/* Terminate GSUP session with EUSE */
gsup_msg.message_type = OSMO_GSUP_MSGT_PROC_SS_ERROR;
OSMO_STRLCPY_ARRAY(gsup_msg.imsi, trans->vsub->imsi);
gsup_msg.session_state = OSMO_GSUP_SESSION_STATE_END;
gsup_msg.session_id = trans->callref;
gsup_msg.cause = GMM_CAUSE_NET_FAIL;
osmo_gsup_client_enc_send(trans->net->vlr->gsup_client, &gsup_msg);
/* Finally, release this transaction */
trans_free(trans);
}
/* Entry point for call independent MO SS messages */
int gsm0911_rcv_nc_ss(struct ran_conn *conn, struct msgb *msg)
{
struct gsm48_hdr *gh = msgb_l3(msg);
struct osmo_gsup_message gsup_msg;
struct gsm_trans *trans;
struct msgb *gsup_msgb;
uint16_t facility_ie_len;
uint8_t *facility_ie;
uint8_t tid;
uint8_t msg_type;
int rc;
msg_type = gsm48_hdr_msg_type(gh);
tid = gsm48_hdr_trans_id_flip_ti(gh);
/* Associate logging messages with this subscriber */
log_set_context(LOG_CTX_VLR_SUBSCR, conn->vsub);
DEBUGP(DMM, "Received SS/USSD data (trans_id=%x, msg_type=%s)\n",
tid, gsm48_pdisc_msgtype_name(GSM48_PDISC_NC_SS, msg_type));
/* Reuse existing transaction, or create a new one */
trans = trans_find_by_id(conn, GSM48_PDISC_NC_SS, tid);
if (!trans) {
/* Count MS-initiated attempts to establish a NC SS/USSD session */
rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_NC_SS_MO_REQUESTS]);
/**
* According to GSM TS 04.80, section 2.4.2 "Register
* (mobile station to network direction)", the REGISTER
* message is sent by the mobile station to the network
* to assign a new transaction identifier for call independent
* supplementary service control and to request or acknowledge
* a supplementary service.
*/
if (msg_type != GSM0480_MTYPE_REGISTER) {
LOGP(DMM, LOGL_ERROR, "Unexpected message (msg_type=%s), "
"transaction is not allocated yet\n",
gsm48_pdisc_msgtype_name(GSM48_PDISC_NC_SS, msg_type));
gsm48_tx_simple(conn,
GSM48_PDISC_NC_SS | (tid << 4),
GSM0480_MTYPE_RELEASE_COMPLETE);
return -EINVAL;
}
DEBUGP(DMM, " -> (new transaction)\n");
trans = trans_alloc(conn->network, conn->vsub,
GSM48_PDISC_NC_SS, tid, new_callref++);
if (!trans) {
LOGP(DMM, LOGL_ERROR, " -> No memory for trans\n");
gsm48_tx_simple(conn,
GSM48_PDISC_NC_SS | (tid << 4),
GSM0480_MTYPE_RELEASE_COMPLETE);
return -ENOMEM;
}
/* Init inactivity timer */
osmo_timer_setup(&trans->ss.timer_guard,
ncss_session_timeout_handler, trans);
/* Count active NC SS/USSD sessions */
osmo_counter_inc(conn->network->active_nc_ss);
trans->conn = ran_conn_get(conn, RAN_CONN_USE_TRANS_NC_SS);
trans->dlci = OMSC_LINKID_CB(msg);
cm_service_request_concludes(conn, msg);
}
/* (Re)schedule the inactivity timer */
if (conn->network->ncss_guard_timeout > 0) {
osmo_timer_schedule(&trans->ss.timer_guard,
conn->network->ncss_guard_timeout, 0);
}
/* Attempt to extract Facility IE */
rc = gsm0480_extract_ie_by_tag(gh, msgb_l3len(msg),
&facility_ie, &facility_ie_len, GSM0480_IE_FACILITY);
if (rc) {
LOGP(DMM, LOGL_ERROR, "GSM 04.80 message parsing error, "
"couldn't extract Facility IE\n");
goto error;
}
/* Facility IE is optional for RELEASE COMPLETE */
if (msg_type != GSM0480_MTYPE_RELEASE_COMPLETE) {
if (!facility_ie || facility_ie_len < 2) {
LOGP(DMM, LOGL_ERROR, "GSM 04.80 message parsing error, "
"missing mandatory Facility IE\n");
rc = -EINVAL;
goto error;
}
}
/* Compose a mew GSUP message */
memset(&gsup_msg, 0x00, sizeof(gsup_msg));
gsup_msg.message_type = OSMO_GSUP_MSGT_PROC_SS_REQUEST;
gsup_msg.session_id = trans->callref;
/**
* Perform A-interface to GSUP-interface mapping,
* according to GSM TS 09.11, table 4.2.
*/
switch (msg_type) {
case GSM0480_MTYPE_REGISTER:
gsup_msg.session_state = OSMO_GSUP_SESSION_STATE_BEGIN;
break;
case GSM0480_MTYPE_FACILITY:
gsup_msg.session_state = OSMO_GSUP_SESSION_STATE_CONTINUE;
break;
case GSM0480_MTYPE_RELEASE_COMPLETE:
gsup_msg.session_state = OSMO_GSUP_SESSION_STATE_END;
break;
}
/* Fill in the (optional) message payload */
if (facility_ie) {
gsup_msg.ss_info_len = facility_ie_len;
gsup_msg.ss_info = facility_ie;
}
/* Fill in subscriber's IMSI */
OSMO_STRLCPY_ARRAY(gsup_msg.imsi, conn->vsub->imsi);
/* Allocate GSUP message buffer */
gsup_msgb = osmo_gsup_client_msgb_alloc();
if (!gsup_msgb) {
LOGP(DMM, LOGL_ERROR, "Couldn't allocate GSUP message\n");
rc = -ENOMEM;
goto error;
}
/* Encode GSUP message */
rc = osmo_gsup_encode(gsup_msgb, &gsup_msg);
if (rc) {
LOGP(DMM, LOGL_ERROR, "Couldn't encode GSUP message\n");
goto error;
}
/* Finally send */
rc = osmo_gsup_client_send(conn->network->vlr->gsup_client, gsup_msgb);
if (rc) {
LOGP(DMM, LOGL_ERROR, "Couldn't send GSUP message\n");
goto error;
}
/* Should we release connection? Or wait for response? */
if (msg_type == GSM0480_MTYPE_RELEASE_COMPLETE)
trans_free(trans);
else
ran_conn_communicating(conn);
/* Count established MS-initiated NC SS/USSD sessions */
if (msg_type == GSM0480_MTYPE_REGISTER)
rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_NC_SS_MO_ESTABLISHED]);
return 0;
error:
/* Abort transaction on DTAP-interface */
msc_send_ussd_reject(conn, tid, -1,
GSM_0480_PROBLEM_CODE_TAG_GENERAL,
GSM_0480_GEN_PROB_CODE_UNRECOGNISED);
if (trans)
trans_free(trans);
/* TODO: abort transaction on GSUP interface if any */
return rc;
}
/* Call-back from paging the B-end of the connection */
static int handle_paging_event(unsigned int hooknum, unsigned int event,
struct msgb *msg, void *_conn, void *_transt)
{
struct ran_conn *conn = _conn;
enum gsm_paging_event paging_event = event;
struct gsm_trans *transt = _transt;
struct gsm48_hdr *gh;
struct msgb *ss_msg;
OSMO_ASSERT(!transt->conn);
OSMO_ASSERT(transt->ss.msg);
switch (paging_event) {
case GSM_PAGING_SUCCEEDED:
DEBUGP(DMM, "Paging subscr %s succeeded!\n",
vlr_subscr_msisdn_or_name(transt->vsub));
/* Assign connection */
transt->conn = ran_conn_get(conn, RAN_CONN_USE_TRANS_NC_SS);
transt->paging_request = NULL;
/* (Re)schedule the inactivity timer */
if (conn->network->ncss_guard_timeout > 0) {
osmo_timer_schedule(&transt->ss.timer_guard,
conn->network->ncss_guard_timeout, 0);
}
/* Send stored message */
ss_msg = transt->ss.msg;
gh = (struct gsm48_hdr *) msgb_push(ss_msg, sizeof(*gh));
gh->proto_discr = GSM48_PDISC_NC_SS;
gh->proto_discr |= transt->transaction_id << 4;
gh->msg_type = GSM0480_MTYPE_REGISTER;
/* Sent to the MS, give ownership of ss_msg */
msc_tx_dtap(transt->conn, ss_msg);
transt->ss.msg = NULL;
/* Count established network-initiated NC SS/USSD sessions */
rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_NC_SS_MT_ESTABLISHED]);
break;
case GSM_PAGING_EXPIRED:
case GSM_PAGING_BUSY:
DEBUGP(DMM, "Paging subscr %s %s!\n",
vlr_subscr_msisdn_or_name(transt->vsub),
paging_event == GSM_PAGING_EXPIRED ? "expired" : "busy");
/* TODO: inform HLR about this failure */
msgb_free(transt->ss.msg);
transt->ss.msg = NULL;
transt->callref = 0;
transt->paging_request = NULL;
trans_free(transt);
break;
}
return 0;
}
static struct gsm_trans *establish_nc_ss_trans(struct gsm_network *net,
struct vlr_subscr *vsub, struct osmo_gsup_message *gsup_msg)
{
struct ran_conn *conn;
struct gsm_trans *trans, *transt;
int tid;
if (gsup_msg->session_state != OSMO_GSUP_SESSION_STATE_BEGIN) {
LOGP(DMM, LOGL_ERROR, "Received non-BEGIN message "
"for non-existing transaction\n");
return NULL;
}
if (!gsup_msg->ss_info || gsup_msg->ss_info_len < 2) {
LOGP(DMM, LOGL_ERROR, "Missing mandatory Facility IE\n");
return NULL;
}
/* If subscriber is not "attached" */
if (!vsub->cgi.lai.lac) {
LOGP(DMM, LOGL_ERROR, "Network-originated session "
"rejected - subscriber is not attached\n");
return NULL;
}
DEBUGP(DMM, "Establishing network-originated session\n");
/* Allocate a new transaction */
trans = trans_alloc(net, vsub, GSM48_PDISC_NC_SS,
TRANS_ID_UNASSIGNED, gsup_msg->session_id);
if (!trans) {
LOGP(DMM, LOGL_ERROR, " -> No memory for trans\n");
return NULL;
}
/* Count active NC SS/USSD sessions */
osmo_counter_inc(net->active_nc_ss);
/* Assign transaction ID */
tid = trans_assign_trans_id(trans->net, trans->vsub, GSM48_PDISC_NC_SS);
if (tid < 0) {
LOGP(DMM, LOGL_ERROR, "No free transaction ID\n");
/* TODO: inform HLR about this */
/* TODO: release connection with subscriber */
trans->callref = 0;
trans_free(trans);
return NULL;
}
trans->transaction_id = tid;
/* Init inactivity timer */
osmo_timer_setup(&trans->ss.timer_guard,
ncss_session_timeout_handler, trans);
/* Attempt to find connection */
conn = connection_for_subscr(vsub);
if (conn) {
/* Assign connection */
trans->conn = ran_conn_get(conn, RAN_CONN_USE_TRANS_NC_SS);
trans->dlci = 0x00; /* SAPI=0, not SACCH */
return trans;
}
DEBUGP(DMM, "Triggering Paging Request\n");
/* Find transaction with this subscriber already paging */
llist_for_each_entry(transt, &net->trans_list, entry) {
/* Transaction of our conn? */
if (transt == trans || transt->vsub != vsub)
continue;
LOGP(DMM, LOGL_ERROR, "Paging already started, "
"rejecting message...\n");
trans_free(trans);
return NULL;
}
/* Trigger Paging Request */
trans->paging_request = subscr_request_conn(vsub,
&handle_paging_event, trans, "GSM 09.11 SS/USSD",
SGSAP_SERV_IND_CS_CALL);
if (!trans->paging_request) {
LOGP(DMM, LOGL_ERROR, "Failed to allocate paging token\n");
trans_free(trans);
return NULL;
}
/* Store the Facility IE to be sent */
OSMO_ASSERT(trans->ss.msg == NULL);
trans->ss.msg = gsm48_msgb_alloc_name("GSM 04.08 SS/USSD");
msgb_tlv_put(trans->ss.msg, GSM0480_IE_FACILITY,
gsup_msg->ss_info_len, gsup_msg->ss_info);
return NULL;
}
/* NC SS specific transaction release.
* Gets called by trans_free, DO NOT CALL YOURSELF! */
void _gsm911_nc_ss_trans_free(struct gsm_trans *trans)
{
/**
* TODO: if transaction wasn't properly terminated,
* we need to do it here by releasing the subscriber
* connection and sending notification via GSUP...
*/
if (trans->ss.msg != NULL)
msgb_free(trans->ss.msg);
/* Stop inactivity timer */
osmo_timer_del(&trans->ss.timer_guard);
/* One session less */
osmo_counter_dec(trans->net->active_nc_ss);
}
int gsm0911_gsup_handler(struct vlr_subscr *vsub,
struct osmo_gsup_message *gsup_msg)
{
struct vlr_instance *vlr;
struct gsm_network *net;
struct gsm_trans *trans;
struct gsm48_hdr *gh;
struct msgb *ss_msg;
bool trans_end;
/* Associate logging messages with this subscriber */
log_set_context(LOG_CTX_VLR_SUBSCR, vsub);
/* Obtain pointer to vlr_instance */
vlr = vsub->vlr;
OSMO_ASSERT(vlr);
/* Obtain pointer to gsm_network */
net = (struct gsm_network *) vlr->user_ctx;
OSMO_ASSERT(net);
/* Handle errors */
if (OSMO_GSUP_IS_MSGT_ERROR(gsup_msg->message_type)) {
/* FIXME: handle this error somehow! */
return 0;
}
/* Attempt to find DTAP-transaction */
trans = trans_find_by_callref(net, gsup_msg->session_id);
if (!trans) {
/* Count network-initiated attempts to establish a NC SS/USSD session */
rate_ctr_inc(&net->msc_ctrs->ctr[MSC_CTR_NC_SS_MT_REQUESTS]);
/* Attempt to establish a new transaction */
trans = establish_nc_ss_trans(net, vsub, gsup_msg);
if (!trans) {
/* FIXME: send ERROR back to the HLR */
return -EINVAL;
}
/* Wait for Paging Response */
if (trans->paging_request)
return 0;
}
/* (Re)schedule the inactivity timer */
if (net->ncss_guard_timeout > 0) {
osmo_timer_schedule(&trans->ss.timer_guard,
net->ncss_guard_timeout, 0);
}
/* Allocate and prepare a new MT message */
ss_msg = gsm48_msgb_alloc_name("GSM 04.08 SS/USSD");
gh = (struct gsm48_hdr *) msgb_push(ss_msg, sizeof(*gh));
gh->proto_discr = GSM48_PDISC_NC_SS;
gh->proto_discr |= trans->transaction_id << 4;
/**
* Perform GSUP-interface to A-interface mapping,
* according to GSM TS 09.11, table 4.1.
*
* TODO: see (note 3), both CONTINUE and END may
* be also mapped to REGISTER if a new transaction
* has to be established.
*/
switch (gsup_msg->session_state) {
case OSMO_GSUP_SESSION_STATE_BEGIN:
gh->msg_type = GSM0480_MTYPE_REGISTER;
break;
case OSMO_GSUP_SESSION_STATE_CONTINUE:
gh->msg_type = GSM0480_MTYPE_FACILITY;
break;
case OSMO_GSUP_SESSION_STATE_END:
gh->msg_type = GSM0480_MTYPE_RELEASE_COMPLETE;
break;
/* Missing or incorrect session state */
case OSMO_GSUP_SESSION_STATE_NONE:
default:
LOGP(DMM, LOGL_ERROR, "Unexpected session state %d\n",
gsup_msg->session_state);
/* FIXME: send ERROR back to the HLR */
msgb_free(ss_msg);
return -EINVAL;
}
/* Facility IE is optional only for RELEASE COMPLETE */
if (gh->msg_type != GSM0480_MTYPE_RELEASE_COMPLETE) {
if (!gsup_msg->ss_info || gsup_msg->ss_info_len < 2) {
LOGP(DMM, LOGL_ERROR, "Missing mandatory Facility IE "
"for mapped 0x%02x message\n", gh->msg_type);
/* FIXME: send ERROR back to the HLR */
msgb_free(ss_msg);
return -EINVAL;
}
}
/* Append Facility IE if preset */
if (gsup_msg->ss_info && gsup_msg->ss_info_len > 2) {
/* Facility IE carries LV, others carry TLV */
if (gh->msg_type == GSM0480_MTYPE_FACILITY)
msgb_lv_put(ss_msg, gsup_msg->ss_info_len, gsup_msg->ss_info);
else
msgb_tlv_put(ss_msg, GSM0480_IE_FACILITY,
gsup_msg->ss_info_len, gsup_msg->ss_info);
}
/* Should we release the transaction? */
trans_end = (gh->msg_type == GSM0480_MTYPE_RELEASE_COMPLETE);
/* Sent to the MS, give ownership of ss_msg */
msc_tx_dtap(trans->conn, ss_msg);
/* Release transaction if required */
if (trans_end)
trans_free(trans);
/* Count established network-initiated NC SS/USSD sessions */
if (gsup_msg->session_state == OSMO_GSUP_SESSION_STATE_BEGIN)
rate_ctr_inc(&net->msc_ctrs->ctr[MSC_CTR_NC_SS_MT_ESTABLISHED]);
return 0;
}