/* (C) 2008-2009 by Harald Welte * (C) 2008, 2009 by Holger Hans Peter Freyther * (C) 2009 by Mike Haben * (C) 2018 by Vadim Yanitskiy * * 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 . * */ /** * MSC-specific handling of call independent Supplementary * Services messages (NC_SS) according to GSM TS 09.11 * "Signalling interworking for supplementary services". */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* 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; /* The timeout might be disabled from the VTY */ if (trans->net->ncss_guard_timeout == 0) return; LOG_TRANS(trans, LOGL_NOTICE, "SS/USSD session timeout, releasing\n"); /* Indicate connection release to subscriber (if active) */ if (trans->msc_a != NULL) { /* This pair of cause location and value is used by commercial networks */ msc_send_ussd_release_complete_cause(trans->msc_a, trans->transaction_id, GSM48_CAUSE_LOC_PUN_S_LU, GSM48_CC_CAUSE_NORMAL_UNSPEC); } /* Terminate GSUP session with EUSE */ gsup_msg = (struct osmo_gsup_message){ .message_type = OSMO_GSUP_MSGT_PROC_SS_ERROR, .session_state = OSMO_GSUP_SESSION_STATE_END, .session_id = trans->callref, .cause = GMM_CAUSE_NET_FAIL, .message_class = OSMO_GSUP_MESSAGE_CLASS_USSD, }; OSMO_STRLCPY_ARRAY(gsup_msg.imsi, trans->vsub->imsi); gsup_client_mux_tx(trans->net->gcm, &gsup_msg); /* Finally, release this transaction */ trans_free(trans); } /* Entry point for call independent MO SS messages */ int gsm0911_rcv_nc_ss(struct msc_a *msc_a, struct msgb *msg) { struct gsm_network *net; struct vlr_subscr *vsub; struct gsm48_hdr *gh = msgb_l3(msg); struct osmo_gsup_message gsup_msg; struct gsm_trans *trans; uint16_t facility_ie_len; uint8_t *facility_ie; uint8_t tid; uint8_t msg_type; int rc; net = msc_a_net(msc_a); OSMO_ASSERT(net); vsub = msc_a_vsub(msc_a); if (!vsub) { LOG_MSC_A(msc_a, LOGL_ERROR, "No vlr_subscr set for this conn\n"); return -EINVAL; } 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, vsub); /* Reuse existing transaction, or create a new one */ trans = trans_find_by_id(msc_a, TRANS_USSD, tid); if (!trans) { /* Count MS-initiated attempts to establish a NC SS/USSD session */ rate_ctr_inc(rate_ctr_group_get_ctr(net->msc_ctrs, 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(DSS, LOGL_ERROR, "Rx %s message for non-existing transaction (tid-%u)\n", gsm48_pdisc_msgtype_name(GSM48_PDISC_NC_SS, msg_type), gsm48_hdr_trans_id(gh)); gsm48_tx_simple(msc_a, GSM48_PDISC_NC_SS | (tid << 4), GSM0480_MTYPE_RELEASE_COMPLETE); return -EINVAL; } trans = trans_alloc(net, vsub, TRANS_USSD, tid, new_callref++); if (!trans) { LOGP(DSS, LOGL_ERROR, " -> No memory for trans\n"); gsm48_tx_simple(msc_a, 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_stat_item_inc(osmo_stat_item_group_get_item(net->statg, MSC_STAT_ACTIVE_NC_SS), 1); trans->dlci = OMSC_LINKID_CB(msg); trans->msc_a = msc_a; msc_a_get(msc_a, MSC_A_USE_NC_SS); osmo_fsm_inst_dispatch(msc_a->c.fi, MSC_A_EV_TRANSACTION_ACCEPTED, trans); /* An earlier CM Service Request for this SS message now has concluded */ if (!osmo_use_count_by(&msc_a->use_count, MSC_A_USE_CM_SERVICE_SS)) LOG_MSC_A(msc_a, LOGL_ERROR, "Creating new MO SS transaction without prior CM Service Request\n"); else msc_a_put(msc_a, MSC_A_USE_CM_SERVICE_SS); } LOG_TRANS(trans, LOGL_DEBUG, "Received SS/USSD msg %s\n", gsm48_pdisc_msgtype_name(GSM48_PDISC_NC_SS, msg_type)); /* (Re)schedule the inactivity timer */ if (net->ncss_guard_timeout > 0) { osmo_timer_schedule(&trans->ss.timer_guard, net->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) { LOG_TRANS(trans, 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) { LOG_TRANS(trans, LOGL_ERROR, "GSM 04.80 message parsing error," " missing mandatory Facility IE\n"); rc = -EINVAL; goto error; } } /* Compose a mew GSUP message */ gsup_msg = (struct osmo_gsup_message){ .message_type = OSMO_GSUP_MSGT_PROC_SS_REQUEST, .session_id = trans->callref, .message_class = OSMO_GSUP_MESSAGE_CLASS_USSD, }; /** * 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, vsub->imsi); rc = gsup_client_mux_tx(trans->net->gcm, &gsup_msg); /* Should we release connection? Or wait for response? */ if (msg_type == GSM0480_MTYPE_RELEASE_COMPLETE) trans_free(trans); /* Count established MS-initiated NC SS/USSD sessions */ if (msg_type == GSM0480_MTYPE_REGISTER) rate_ctr_inc(rate_ctr_group_get_ctr(net->msc_ctrs, MSC_CTR_NC_SS_MO_ESTABLISHED)); return rc; error: /* Abort transaction on DTAP-interface */ msc_send_ussd_reject(msc_a, 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 void ss_paging_cb(struct msc_a *msc_a, struct gsm_trans *trans) { struct gsm48_hdr *gh; struct msgb *ss_msg; if (trans->msc_a) { LOG_MSC_A_CAT(msc_a, DPAG, LOGL_ERROR, "Handle paging error: transaction already associated with subscriber," " apparently it was already handled. Skip.\n"); return; } OSMO_ASSERT(trans->ss.msg); if (msc_a) { struct gsm_network *net = msc_a_net(msc_a); LOG_MSC_A_CAT(msc_a, DSS, LOGL_DEBUG, "Paging succeeded\n"); /* Assign connection */ msc_a_get(msc_a, MSC_A_USE_NC_SS); trans->msc_a = msc_a; trans->paging_request = NULL; /* (Re)schedule the inactivity timer */ if (net->ncss_guard_timeout > 0) { osmo_timer_schedule(&trans->ss.timer_guard, net->ncss_guard_timeout, 0); } /* Send stored message */ ss_msg = trans->ss.msg; gh = (struct gsm48_hdr *) msgb_push(ss_msg, sizeof(*gh)); gh->proto_discr = GSM48_PDISC_NC_SS; gh->proto_discr |= trans->transaction_id << 4; gh->msg_type = GSM0480_MTYPE_REGISTER; /* Sent to the MS, give ownership of ss_msg */ msc_a_tx_dtap_to_i(msc_a, ss_msg); trans->ss.msg = NULL; /* Count established network-initiated NC SS/USSD sessions */ rate_ctr_inc(rate_ctr_group_get_ctr(net->msc_ctrs, MSC_CTR_NC_SS_MT_ESTABLISHED)); } else { struct osmo_gsup_message gsup_msg; LOG_MSC_A_CAT(msc_a, DSS, LOGL_DEBUG, "Paging expired\n"); gsup_msg = (struct osmo_gsup_message){ .message_class = OSMO_GSUP_MESSAGE_CLASS_USSD, .message_type = OSMO_GSUP_MSGT_PROC_SS_ERROR, .session_state = OSMO_GSUP_SESSION_STATE_END, .session_id = trans->callref, /* FIXME: we need message class specific cause values */ .cause = GMM_CAUSE_IMPL_DETACHED, }; /* Fill in subscriber's IMSI */ OSMO_STRLCPY_ARRAY(gsup_msg.imsi, trans->vsub->imsi); /* Inform HLR/EUSE about the failure */ gsup_client_mux_tx(trans->net->gcm, &gsup_msg); msgb_free(trans->ss.msg); trans->ss.msg = NULL; trans->callref = 0; trans->paging_request = NULL; trans_free(trans); } } static struct gsm_trans *establish_nc_ss_trans(struct gsm_network *net, struct vlr_subscr *vsub, const struct osmo_gsup_message *gsup_msg) { struct msc_a *msc_a; struct gsm_trans *trans; int tid; if (gsup_msg->session_state != OSMO_GSUP_SESSION_STATE_BEGIN) { LOGP(DSS, LOGL_ERROR, "Received non-BEGIN message for non-existing transaction\n"); return NULL; } LOGP(DSS, LOGL_DEBUG, "(%s) Establishing a network-originated session (id=0x%x)\n", vlr_subscr_name(vsub), gsup_msg->session_id); if (!gsup_msg->ss_info || gsup_msg->ss_info_len < 2) { LOGP(DSS, LOGL_ERROR, "Missing mandatory Facility IE\n"); return NULL; } /* Obtain an unused transaction ID */ tid = trans_assign_trans_id(net, vsub, TRANS_USSD); if (tid < 0) { LOGP(DSS, LOGL_ERROR, "No free transaction ID\n"); return NULL; } /* Allocate a new NCSS transaction */ trans = trans_alloc(net, vsub, TRANS_USSD, tid, gsup_msg->session_id); if (!trans) { LOGP(DSS, LOGL_ERROR, " -> No memory for trans\n"); return NULL; } /* Count active NC SS/USSD sessions */ osmo_stat_item_inc(osmo_stat_item_group_get_item(net->statg, MSC_STAT_ACTIVE_NC_SS), 1); /* Init inactivity timer */ osmo_timer_setup(&trans->ss.timer_guard, ncss_session_timeout_handler, trans); /* Attempt to find connection */ msc_a = msc_a_for_vsub(vsub, true); if (msc_a) { /* Assign connection */ msc_a_get(msc_a, MSC_A_USE_NC_SS); trans->msc_a = msc_a; trans->dlci = 0x00; /* SAPI=0, not SACCH */ return trans; } LOG_TRANS(trans, LOGL_DEBUG, "Triggering Paging Request\n"); /* Trigger Paging Request */ trans->paging_request = paging_request_start(vsub, PAGING_CAUSE_SIGNALLING_HIGH_PRIO, ss_paging_cb, trans, "GSM 09.11 SS/USSD"); if (!trans->paging_request) { LOG_TRANS(trans, 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 trans; } /* 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_stat_item_dec(osmo_stat_item_group_get_item(trans->net->statg, MSC_STAT_ACTIVE_NC_SS), 1); } int gsm0911_gsup_rx(struct gsup_client_mux *gcm, void *data, const struct osmo_gsup_message *gsup_msg) { struct gsm_network *net = (struct gsm_network *) data; struct gsm_trans *trans; struct gsm48_hdr *gh; struct msgb *ss_msg; bool trans_end; struct msc_a *msc_a; struct vlr_subscr *vsub; vsub = vlr_subscr_find_by_imsi(net->vlr, gsup_msg->imsi, __func__); if (!vsub) { LOGP(DSS, LOGL_ERROR, "Rx %s for unknown subscriber, rejecting\n", osmo_gsup_message_type_name(gsup_msg->message_type)); gsup_client_mux_tx_error_reply(gcm, gsup_msg, GMM_CAUSE_IMSI_UNKNOWN); return -GMM_CAUSE_IMSI_UNKNOWN; } /* Associate logging messages with this subscriber */ log_set_context(LOG_CTX_VLR_SUBSCR, vsub); /* Attempt to find DTAP-transaction */ trans = trans_find_by_callref(net, TRANS_USSD, gsup_msg->session_id); /* Handle errors */ if (OSMO_GSUP_IS_MSGT_ERROR(gsup_msg->message_type)) { LOGP(DSS, LOGL_NOTICE, "Rx %s from HLR/EUSE (cause=0x%02x, sid=0x%x)\n", osmo_gsup_message_type_name(gsup_msg->message_type), gsup_msg->cause, gsup_msg->session_id); /* We don't need subscriber info anymore */ vlr_subscr_put(vsub, __func__); if (!trans) { LOGP(DSS, LOGL_ERROR, "No transaction found for " "sid=0x%x, nothing to abort\n", gsup_msg->session_id); return -ENODEV; } LOG_TRANS(trans, LOGL_NOTICE, "Aborting the session: sending RELEASE COMPLETE\n"); /* Indicate connection release to subscriber (if active) */ if (trans->msc_a != NULL) { /* TODO: implement GSUP - GSM 04.80 cause mapping */ msc_send_ussd_release_complete_cause(trans->msc_a, trans->transaction_id, GSM48_CAUSE_LOC_PUN_S_LU, GSM48_CC_CAUSE_TEMP_FAILURE); } /* Terminate transaction */ trans_free(trans); return 0; } if (!trans) { /* Count network-initiated attempts to establish a NC SS/USSD session */ rate_ctr_inc(rate_ctr_group_get_ctr(net->msc_ctrs, MSC_CTR_NC_SS_MT_REQUESTS)); /* Attempt to establish a new transaction */ trans = establish_nc_ss_trans(net, vsub, gsup_msg); if (!trans) { LOGP(DSS, LOGL_ERROR, "Failed to establish a network-originated " "SS/USSD transaction, rejecting %s\n", osmo_gsup_message_type_name(gsup_msg->message_type)); gsup_client_mux_tx_error_reply(gcm, gsup_msg, GMM_CAUSE_NET_FAIL); vlr_subscr_put(vsub, __func__); return -EINVAL; } /* Wait for Paging Response */ if (trans->paging_request) { vlr_subscr_put(vsub, __func__); return 0; } } /* We don't need subscriber info anymore */ vlr_subscr_put(vsub, __func__); /* (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: LOG_TRANS(trans, LOGL_ERROR, "Unexpected session state %d\n", gsup_msg->session_state); gsup_client_mux_tx_error_reply(gcm, gsup_msg, GMM_CAUSE_MSGT_INCOMP_P_STATE); 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) { LOG_TRANS(trans, LOGL_ERROR, "Missing mandatory Facility IE " "for mapped 0x%02x message\n", gh->msg_type); gsup_client_mux_tx_error_reply(gcm, gsup_msg, GMM_CAUSE_INV_MAND_INFO); 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_a = trans->msc_a; if (!msc_a) { LOG_TRANS(trans, LOGL_ERROR, "Cannot send SS message, no local MSC-A role defined for subscriber\n"); gsup_client_mux_tx_error_reply(gcm, gsup_msg, GMM_CAUSE_NET_FAIL); msgb_free(ss_msg); return -EINVAL; } msc_a_tx_dtap_to_i(msc_a, 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(rate_ctr_group_get_ctr(net->msc_ctrs, MSC_CTR_NC_SS_MT_ESTABLISHED)); return 0; }