/* 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(¶m->calling_addr, calling_addr, sizeof(*calling_addr)); memcpy(¶m->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, ¶m->calling_addr, ¶m->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; }