libosmo-sccp/src/sccp_scmg.c

319 lines
12 KiB
C

/* SCCP Management (SCMG) according to ITU-T Q.713/Q.714 */
/* (C) 2021 by Harald Welte <laforge@gnumonks.org>
* All Rights reserved
*
* SPDX-License-Identifier: GPL-2.0+
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <string.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/timer.h>
#include <osmocom/core/fsm.h>
#include <osmocom/sigtran/sccp_sap.h>
#include <osmocom/sigtran/protocol/sua.h>
#include <osmocom/sigtran/protocol/sccp_scmg.h>
#include <osmocom/sccp/sccp_types.h>
#include "xua_internal.h"
#include "sccp_internal.h"
/* ITU-T Q.714 5.3.3 Subsystem allowed */
void sccp_scmg_rx_ssn_allowed(struct osmo_sccp_instance *inst, uint32_t dpc, uint32_t ssn, uint32_t smi)
{
struct osmo_scu_state_param state;
/* 1) Instruct the translation function to update the translation tables */
/* 2) Mark as "allowed" the status of that subsystem. */
/* 3) Initiate a local broadcast of "User-in-service" information for the allowed subsystem */
state = (struct osmo_scu_state_param) {
.affected_pc = dpc,
.affected_ssn = ssn,
.user_in_service = true,
.ssn_multiplicity_ind = smi,
};
sccp_lbcs_local_bcast_state(inst, &state);
/* 4) Discontinue the subsystem status test if such a test was in progress */
/* 5) Initiate a broadcast of Subsystem-Allowed messages to concerned signalling points. */
}
/* ITU-T Q.714 5.3.2 Subsystem prohibited */
void sccp_scmg_rx_ssn_prohibited(struct osmo_sccp_instance *inst, uint32_t dpc, uint32_t ssn, uint32_t smi)
{
struct osmo_scu_state_param state;
/* 1) instruct the translation function to update the translation tables */
/* 2) mark as "prohibited" the status of that subsystem */
/* 3) initiate a local broadcast of "User-out-of-service" information */
state = (struct osmo_scu_state_param) {
.affected_pc = dpc,
.affected_ssn = ssn,
.user_in_service = false,
.ssn_multiplicity_ind = smi,
};
sccp_lbcs_local_bcast_state(inst, &state);
/* 4) initiate the subsystem status test procedure if the prohibited subsystem is not local */
/* 5) initiate a broadcast of Subsystem-Prohibited messages to concerned SP */
/* 6) cancel "ignore subsystem status test" and the associated timer if in progress and if
* the newly prohibited subsystem resides at the local node. */
}
/*! brief MTP -> SNM (MTP-PAUSE.ind) - inability to providing MTP service Q.714 5.2.2 */
void sccp_scmg_rx_mtp_pause(struct osmo_sccp_instance *inst, uint32_t dpc)
{
struct osmo_scu_pcstate_param pcstate;
/* 1) Informs the translation function to update the translation tables. */
/* 2) SCCP management marks as "prohibited" the status of the remote signalling point, the
remote SCCP and each subsystem at the remote signalling point. */
/* 3) Discontinues all subsystem status tests (including SSN = 1) */
/* 4) local broadcast of "user-out-of-service" for each SSN at that dest
* [this would require us to track SSNs at each PC, which we don't] */
/* 5) local broadcast of "signaling point inaccessible" */
/* 6) local broadcast of "remote SCCP unavailable" */
pcstate = (struct osmo_scu_pcstate_param) {
.affected_pc = dpc,
.restricted_importance_level = 0,
.sp_status = OSMO_SCCP_SP_S_INACCESSIBLE,
.remote_sccp_status = OSMO_SCCP_REM_SCCP_S_UNAVAILABLE_UNKNOWN,
};
sccp_lbcs_local_bcast_pcstate(inst, &pcstate);
}
/*! brief MTP -> SNM (MTP-RESUME.ind) - ability of providing the MTP service Q.714 5.2.3 */
void sccp_scmg_rx_mtp_resume(struct osmo_sccp_instance *inst, uint32_t dpc)
{
struct osmo_scu_pcstate_param pcstate;
/* 1) Sets the congestion state of that signalling point */
/* 2) Instructs the translation function to update the translation tables. */
/* 3) Marks as "allowed" the status of that destination, and the SCCP */
/* 4) - not applicable */
/* 5) Marks as "allowed" the status of remote subsystems */
/* 6) local broadcast of "signalling point accessible" */
/* 7) local broadcast of "remote SCCP accessible" */
pcstate = (struct osmo_scu_pcstate_param) {
.affected_pc = dpc,
.restricted_importance_level = 0,
.sp_status = OSMO_SCCP_SP_S_ACCESSIBLE,
.remote_sccp_status = OSMO_SCCP_REM_SCCP_S_AVAILABLE,
};
sccp_lbcs_local_bcast_pcstate(inst, &pcstate);
/* 8) local broadcast of "user-in-service"
* [this would require us to track SSNs at each PC, which we don't] */
}
void sccp_scmg_rx_mtp_status(struct osmo_sccp_instance *inst, uint32_t dpc, enum mtp_unavail_cause cause)
{
struct osmo_scu_pcstate_param pcstate;
/* 1) Informs the translation function to update the translation tables. */
/* 2) In the case where the SCCP has received an MTP-STATUS indication primitive relating to
Mark the status of the SCCP and each SSN for the relevant destination to "prohibited"
and initiates a subsystem status test with SSN = 1. If the cause in the MTP-STATUS
indication primitive indicates "unequipped user", then no subsystem status test is
initiated. */
/* 3) Discontinues all subsystem status tests (including SSN = 1) if an MTP-STATUS
indication primitive is received with a cause of "unequipped SCCP". The SCCP
discontinues all subsystem status tests, except for SSN = 1, if an MTP-STATUS
indication primitive is received with a cause of either "unknown" or "inaccessible" */
switch (cause) {
case MTP_UNAVAIL_C_UNKNOWN:
case MTP_UNAVAIL_C_UNEQUIP_REM_USER:
case MTP_UNAVAIL_C_INACC_REM_USER:
break;
}
/* 4) local broadcast of "user-out-of-service" for each SSN at that dest
* [this would require us to track SSNs at each PC, which we don't] */
/* 6) local broadcast of "remote SCCP unavailable" */
pcstate = (struct osmo_scu_pcstate_param) {
.affected_pc = dpc,
.restricted_importance_level = 0,
.sp_status = OSMO_SCCP_SP_S_ACCESSIBLE,
.remote_sccp_status = OSMO_SCCP_REM_SCCP_S_UNAVAILABLE_UNKNOWN,
};
sccp_lbcs_local_bcast_pcstate(inst, &pcstate);
}
const struct value_string sccp_scmg_msgt_names[] = {
{ SCCP_SCMG_MSGT_SSA, "SSA (Subsystem Allowed)" },
{ SCCP_SCMG_MSGT_SSP, "SSP (Subsystem Prohibited)" },
{ SCCP_SCMG_MSGT_SST, "SST (Subsystem Status Test)" },
{ SCCP_SCMG_MSGT_SOR, "SOR (Subsystem Out-of-service Request)" },
{ SCCP_SCMG_MSGT_SOG, "SOG (Subsystem Out-of-service Grant)" },
{ SCCP_SCMG_MSGT_SSC, "SSC (Subsystem Congested)" },
{ 0, NULL }
};
static int sccp_scmg_tx(struct osmo_sccp_user *scu, const struct osmo_sccp_addr *calling_addr,
const struct osmo_sccp_addr *called_addr,
uint8_t msg_type, uint8_t ssn, uint16_t pc, uint8_t smi, uint8_t *ssc_cong_lvl)
{
struct msgb *msg = sccp_msgb_alloc(__func__);
struct osmo_scu_prim *prim;
struct osmo_scu_unitdata_param *param;
struct sccp_scmg_msg *scmg;
/* fill primitive header */
prim = (struct osmo_scu_prim *) msgb_put(msg, sizeof(*prim));
param = &prim->u.unitdata;
memcpy(&param->calling_addr, calling_addr, sizeof(*calling_addr));
memcpy(&param->called_addr, called_addr, sizeof(*called_addr));
osmo_prim_init(&prim->oph, SCCP_SAP_USER, OSMO_SCU_PRIM_N_UNITDATA, PRIM_OP_REQUEST, msg);
/* Fill the actual SCMG message */
msg->l2h = msgb_put(msg, sizeof(*scmg));
scmg = (struct sccp_scmg_msg *) msg->l2h;
scmg->msg_type = msg_type;
scmg->affected_ssn = ssn;
scmg->affected_pc = pc;
scmg->smi = smi;
/* add congestion level in case of SSC message */
if (msg_type == SCCP_SCMG_MSGT_SSC) {
msgb_put(msg, 1);
OSMO_ASSERT(ssc_cong_lvl);
scmg->ssc_congestion_lvl[1] = *ssc_cong_lvl;
}
return osmo_sccp_user_sap_down(scu, &prim->oph);
}
/* Subsystem Allowed received */
static int scmg_rx_ssa(struct osmo_sccp_user *scu, const struct osmo_sccp_addr *calling_addr,
const struct osmo_sccp_addr *called_addr, const struct sccp_scmg_msg *ssa)
{
/* Q.714 5.3.3 */
if (ssa->affected_ssn == SCCP_SSN_MANAGEMENT)
return 0;
/* if the SSN is not marked as prohibited, ignore */
/* Q.714 5.3.2.2 a) */
sccp_scmg_rx_ssn_allowed(scu->inst, ssa->affected_pc, ssa->affected_ssn, ssa->smi);
/* If the remote SCCP, at which the subsystem reported in the SSA message resides, is marked
* inaccessible, then the message is treated as an implicit indication of SCCP restart */
return 0;
}
/* Subsystem Prohibited received */
static int scmg_rx_ssp(struct osmo_sccp_user *scu, const struct osmo_sccp_addr *calling_addr,
const struct osmo_sccp_addr *called_addr, const struct sccp_scmg_msg *ssp)
{
/* Q.714 5.3.2.2 a) */
sccp_scmg_rx_ssn_prohibited(scu->inst, ssp->affected_pc, ssp->affected_ssn, ssp->smi);
return 0;
}
/* Subsystem Test received */
static int scmg_rx_sst(struct osmo_sccp_user *scu, const struct osmo_sccp_addr *calling_addr,
const struct osmo_sccp_addr *called_addr, const struct sccp_scmg_msg *sst)
{
/* Q.714 5.3.4.3 Actions at the receiving side (of SST) */
/* check "ignore subsystem status test" and bail out */
/* check if SSN in question is available. If yes, return SSA. If not, ignore */
scu = sccp_user_find(scu->inst, sst->affected_ssn, sst->affected_pc);
if (!scu)
return 0;
/* is subsystem available? */
if (0 /* !subsys_available(scu) */)
return 0;
return sccp_scmg_tx(scu, called_addr, calling_addr, SCCP_SCMG_MSGT_SSA,
sst->affected_ssn, sst->affected_pc, 0, NULL);
}
static int scmg_rx(struct osmo_sccp_user *scu, const struct osmo_sccp_addr *calling_addr,
const struct osmo_sccp_addr *called_addr, const struct sccp_scmg_msg *scmg)
{
switch (scmg->msg_type) {
case SCCP_SCMG_MSGT_SSA:
return scmg_rx_ssa(scu, calling_addr, called_addr, scmg);
case SCCP_SCMG_MSGT_SSP:
return scmg_rx_ssp(scu, calling_addr, called_addr, scmg);
case SCCP_SCMG_MSGT_SST:
return scmg_rx_sst(scu, calling_addr, called_addr, scmg);
case SCCP_SCMG_MSGT_SOR:
case SCCP_SCMG_MSGT_SOG:
case SCCP_SCMG_MSGT_SSC:
default:
LOGP(DLSCCP, LOGL_NOTICE, "Rx unsupported SCCP SCMG %s, ignoring\n",
sccp_scmg_msgt_name(scmg->msg_type));
break;
}
return 0;
}
/* main entry point for SCCP user primitives from SCRC/SCOC */
static int scmg_prim_cb(struct osmo_prim_hdr *oph, void *_scu)
{
struct osmo_sccp_user *scu = _scu;
struct osmo_scu_prim *prim = (struct osmo_scu_prim *) oph;
struct osmo_scu_unitdata_param *param;
struct sccp_scmg_msg *scmg;
int rc = 0;
switch (OSMO_PRIM_HDR(oph)) {
case OSMO_PRIM(OSMO_SCU_PRIM_N_UNITDATA, PRIM_OP_INDICATION):
param = &prim->u.unitdata;
scmg = msgb_l2(oph->msg);
/* ensure minimum length based on message type */
if (msgb_l2len(oph->msg) < sizeof(*scmg)) {
rc = -1;
break;
}
if (scmg->msg_type == SCCP_SCMG_MSGT_SSC && msgb_l2len(oph->msg) < sizeof(*scmg)+1) {
rc = -1;
break;
}
/* interestingly, PC is specified to be encoded in little endian ?!? */
scmg->affected_pc = osmo_load16le(&scmg->affected_pc);
rc = scmg_rx(scu, &param->calling_addr, &param->called_addr, scmg);
break;
case OSMO_PRIM(OSMO_SCU_PRIM_N_PCSTATE, PRIM_OP_INDICATION):
LOGP(DLSCCP, LOGL_DEBUG, "Ignoring SCCP user primitive %s\n", osmo_scu_prim_name(oph));
break;
default:
LOGP(DLSCCP, LOGL_ERROR, "unsupported SCCP user primitive %s\n",
osmo_scu_prim_name(oph));
break;
}
msgb_free(oph->msg);
return rc;
}
/* register SCMG as SCCP user for SSN=1 */
int sccp_scmg_init(struct osmo_sccp_instance *inst)
{
struct osmo_sccp_user *scu;
scu = osmo_sccp_user_bind(inst, "SCCP Management", scmg_prim_cb, SCCP_SSN_MANAGEMENT);
if (!scu)
return -1;
return 0;
}