libosmo-sccp/src/sccp_scrc.c

523 lines
15 KiB
C

/* SCCP Routing Control (SCRC) according to ITU-T Q.714 */
/* (C) 2015-2017 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 <stdbool.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/msgb.h>
#include <osmocom/sccp/sccp_types.h>
#include <osmocom/sigtran/osmo_ss7.h>
#include <osmocom/sigtran/sccp_sap.h>
#include <osmocom/sigtran/sccp_helpers.h>
#include <osmocom/sigtran/protocol/sua.h>
#include <osmocom/sigtran/protocol/mtp.h>
#include "sccp_internal.h"
#include "xua_internal.h"
/***********************************************************************
* Helper Functions
***********************************************************************/
static bool sua_is_connectionless(struct xua_msg *xua)
{
if (xua->hdr.msg_class == SUA_MSGC_CL)
return true;
else
return false;
}
static bool sua_is_cr(struct xua_msg *xua)
{
if (xua->hdr.msg_class == SUA_MSGC_CO &&
xua->hdr.msg_type == SUA_CO_CORE)
return true;
return false;
}
static bool dpc_accessible(struct osmo_sccp_instance *inst, uint32_t pc)
{
/* TODO: implement this! */
return true;
}
static bool sccp_available(struct osmo_sccp_instance *inst,
const struct osmo_sccp_addr *addr)
{
/* TODO: implement this! */
return true;
}
static int sua2sccp_tx_m3ua(struct osmo_sccp_instance *inst,
struct xua_msg *sua)
{
struct msgb *msg;
struct osmo_mtp_prim *omp;
struct osmo_mtp_transfer_param *param;
struct osmo_ss7_instance *s7i = inst->ss7;
uint32_t remote_pc = sua->mtp.dpc;
/* 1) encode the SUA in xua_msg to SCCP message */
msg = osmo_sua_to_sccp(sua);
if (!msg) {
LOGP(DLSCCP, LOGL_ERROR, "Cannot encode SUA to SCCP\n");
return -1;
}
/* 2) wrap into MTP-TRANSFER.req primitive */
msg->l2h = msg->data;
omp = (struct osmo_mtp_prim *) msgb_push(msg, sizeof(*omp));
osmo_prim_init(&omp->oph, MTP_SAP_USER,
OSMO_MTP_PRIM_TRANSFER, PRIM_OP_REQUEST, msg);
param = &omp->u.transfer;
if (sua->mtp.opc)
param->opc = sua->mtp.opc;
else {
if (!osmo_ss7_pc_is_valid(s7i->cfg.primary_pc)) {
LOGP(DLSCCP, LOGL_ERROR, "SS7 instance %u: no primary point-code set\n",
s7i->cfg.id);
return -1;
}
param->opc = s7i->cfg.primary_pc;
}
param->dpc = remote_pc;
param->sls = sua->mtp.sls;
param->sio = MTP_SIO(MTP_SI_SCCP, s7i->cfg.network_indicator);
/* 3) send via MTP-SAP (osmo_ss7_instance) */
return osmo_ss7_user_mtp_xfer_req(s7i, omp);
}
/* Generate MTP-TRANSFER.req from xUA message */
static int gen_mtp_transfer_req_xua(struct osmo_sccp_instance *inst,
struct xua_msg *xua,
const struct osmo_sccp_addr *called)
{
struct osmo_ss7_route *rt;
/* this is a bit fishy due to the different requirements of
* classic SSCP/MTP compared to various SIGTRAN stackings.
* Normally, we would expect a fully encoded SCCP message here,
* but then if the route points to a SUA link, we actually need
* the SUA version of the message.
*
* We need to differentiate the following cases:
* a) SUA: encode XUA to SUA and send via ASP
* b) M3UA: encode XUA to SCCP, create MTP-TRANSFER.req
* primitive and send it via ASP
* c) M2UA/M2PA or CS7: encode XUA, create MTP-TRANSFER.req
* primitive and send it via link
*/
if (called->presence & OSMO_SCCP_ADDR_T_PC)
xua->mtp.dpc = called->pc;
if (!xua->mtp.dpc) {
LOGP(DLSCCP, LOGL_ERROR, "MTP-TRANSFER.req from SCCP "
"without DPC?!? called=%s\n",
osmo_sccp_addr_dump(called));
return -1;
}
rt = osmo_ss7_route_lookup(inst->ss7, xua->mtp.dpc);
if (!rt) {
LOGP(DLSCCP, LOGL_ERROR, "MTP-TRANSFER.req from SCCP for "
"DPC %u: no route!\n", xua->mtp.dpc);
return -1;
}
if (rt->dest.as) {
struct osmo_ss7_as *as = rt->dest.as;
switch (as->cfg.proto) {
case OSMO_SS7_ASP_PROT_SUA:
return sua_tx_xua_as(as, xua);
case OSMO_SS7_ASP_PROT_M3UA:
case OSMO_SS7_ASP_PROT_IPA:
return sua2sccp_tx_m3ua(inst, xua);
default:
LOGP(DLSCCP, LOGL_ERROR, "MTP-TRANSFER.req for "
"unknown protocol %u\n", as->cfg.proto);
break;
}
} else if (rt->dest.linkset) {
LOGP(DLSCCP, LOGL_ERROR, "MTP-TRANSFER.req from SCCP for "
"linkset %s unsupported\n", rt->dest.linkset->cfg.name);
} else {
OSMO_ASSERT(0);
}
return -1;
}
/***********************************************************************
* Global Title Translation
***********************************************************************/
static int translate(struct osmo_sccp_instance *inst,
const struct osmo_sccp_addr *called,
struct osmo_sccp_addr *translated)
{
/* TODO: implement this! */
*translated = *called;
return 0;
}
/***********************************************************************
* Individual SCRC Nodes
***********************************************************************/
static int scrc_local_out_common(struct osmo_sccp_instance *inst,
struct xua_msg *xua,
const struct osmo_sccp_addr *called);
static int scrc_node_12(struct osmo_sccp_instance *inst, struct xua_msg *xua,
const struct osmo_sccp_addr *called)
{
/* TODO: Determine restriction */
/* TODO: Treat Calling Party Addr */
/* TODO: Hop counter */
/* MTP-TRANSFER.req to MTP */
return gen_mtp_transfer_req_xua(inst, xua, called);
}
static int scrc_node_2(struct osmo_sccp_instance *inst, struct xua_msg *xua,
const struct osmo_sccp_addr *called)
{
/* Node 2 on Sheet 5, only CO */
/* Is DPC accessible? */
if (!dpc_accessible(inst, called->pc)) {
/* Error: MTP Failure */
/* Routing Failure SCRC -> SCOC */
sccp_scoc_rx_scrc_rout_fail(inst, xua,
SCCP_RETURN_CAUSE_MTP_FAILURE);
return 0;
}
/* Is SCCP available? */
if (!sccp_available(inst, called)) {
/* Error: SCCP Failure */
/* Routing Failure SCRC -> SCOC */
sccp_scoc_rx_scrc_rout_fail(inst, xua,
SCCP_RETURN_CAUSE_SCCP_FAILURE);
return 0;
}
return scrc_node_12(inst, xua, called);
}
static int scrc_node_7(struct osmo_sccp_instance *inst,
struct xua_msg *xua,
const struct osmo_sccp_addr *called)
{
/* Connection Oriented? */
if (sua_is_connectionless(xua)) {
/* TODO: Perform Capability Test */
/* TODO: Changes Needed? */
if (0) {
/* Changes Needed -> SCLC */
return 0;
}
} else {
/* TODO: Coupling Required? */
if (0) {
/* Node 13 (Sheet 5) */
}
}
return scrc_node_12(inst, xua, called);
}
/* Node 4 (Sheet 3) */
static int scrc_node_4(struct osmo_sccp_instance *inst,
struct xua_msg *xua, uint32_t return_cause)
{
/* TODO: Routing Failure SCRC -> OMAP */
if (sua_is_connectionless(xua)) {
/* Routing Failure SCRC -> SCLC */
sccp_sclc_rx_scrc_rout_fail(inst, xua, return_cause);
} else {
/* Routing Failure SCRC -> SCOC */
sccp_scoc_rx_scrc_rout_fail(inst, xua, return_cause);
}
return 0;
}
static int scrc_translate_node_9(struct osmo_sccp_instance *inst,
struct xua_msg *xua,
const struct osmo_sccp_addr *called)
{
struct osmo_sccp_addr translated;
int rc;
/* Translate */
rc = translate(inst, called, &translated);
/* Node 9 (Sheet 3) */
if (rc < 0) {
/* Node 4 (Sheet 3) */
return scrc_node_4(inst, xua,
SCCP_RETURN_CAUSE_NO_TRANSLATION);
}
/* Route on SSN? */
if (translated.ri != OSMO_SCCP_RI_SSN_PC &&
translated.ri != OSMO_SCCP_RI_SSN_IP) {
/* TODO: GT Routing */
LOGP(DLSCCP, LOGL_NOTICE, "GT Routing not implemented yet\n");
#if 1
/* Prevent endless recursion, see OS#2666. */
sccp_sclc_rx_scrc_rout_fail(inst, xua,
SCCP_RETURN_CAUSE_SUBSYSTEM_FAILURE);
return 0;
#else
/* Node 7 (Sheet 5) */
return scrc_node_7(inst, xua, called);
#endif
}
/* Check DPC resultant from GT translation */
if (osmo_ss7_pc_is_local(inst->ss7, translated.pc)) {
if (sua_is_connectionless(xua)) {
/* CL_MSG -> SCLC */
sccp_sclc_rx_from_scrc(inst, xua);
} else {
/* Node 1 (Sheet 3) */
/* CO_MSG -> SCOC */
sccp_scoc_rx_from_scrc(inst, xua);
}
return 0;
} else {
/* Availability already checked */
/* Node 7 (Sheet 5) */
return scrc_node_7(inst, xua, called);
}
}
/* Node 6 (Sheet 3) */
static int scrc_node_6(struct osmo_sccp_instance *inst,
struct xua_msg *xua,
const struct osmo_sccp_addr *called)
{
struct osmo_sccp_user *scu;
/* it is not really clear that called->pc will be set to
* anything here, in the case of a SSN-only CalledAddr */
scu = sccp_user_find(inst, called->ssn, called->pc);
/* Is subsystem equipped? */
if (!scu) {
/* Error: unequipped user */
LOGP(DLSCCP, LOGL_NOTICE,
"Unable to find user for SSN=%u PC=%s\n",
called->ssn, osmo_ss7_pointcode_print(inst->ss7, called->pc));
return scrc_node_4(inst, xua,
SCCP_RETURN_CAUSE_UNEQUIPPED_USER);
}
/* Is subsystem available? */
if (0 /* !subsys_available(scu) */) {
/* Error: subsystem failure */
/* TODO: SCRC -> SSPC */
if (sua_is_connectionless(xua)) {
/* Routing Failure SCRC -> SCLC */
sccp_sclc_rx_scrc_rout_fail(inst, xua,
SCCP_RETURN_CAUSE_SUBSYSTEM_FAILURE);
} else {
/* Routing Failure SCRC -> SCOC */
sccp_scoc_rx_scrc_rout_fail(inst, xua,
SCCP_RETURN_CAUSE_SUBSYSTEM_FAILURE);
}
return 0;
}
if (sua_is_connectionless(xua)) {
/* CL_MSG -> SCLC */
sccp_sclc_rx_from_scrc(inst, xua);
} else {
/* Node 1 (Sheet 3) */
/* CO_MSG -> SCOC */
sccp_scoc_rx_from_scrc(inst, xua);
}
return 0;
}
static int scrc_local_out_common(struct osmo_sccp_instance *inst,
struct xua_msg *xua,
const struct osmo_sccp_addr *called)
{
struct osmo_ss7_instance *s7i = inst->ss7;
/* Called address includes DPC? */
if (called->presence & OSMO_SCCP_ADDR_T_PC) {
if (!osmo_ss7_pc_is_local(s7i, called->pc)) {
/* Node 7 of sheet 5 */
/* Coupling required: no */
return scrc_node_12(inst, xua, called);
}
/* Called address includes SSN? */
if (called->presence & OSMO_SCCP_ADDR_T_SSN) {
if (/* TODO: check if we are doing global translation && */
(called->presence & OSMO_SCCP_ADDR_T_GT))
return scrc_translate_node_9(inst, xua, called);
else
return scrc_node_6(inst, xua, called);
}
}
/* No SSN in CalledAddr or no DPC included */
if (!(called->presence & OSMO_SCCP_ADDR_T_GT)) {
/* Error reason: Unqualified */
/* TODO: Routing Failure SCRC -> OMAP */
/* Node 4 (Sheet 3) */
return scrc_node_4(inst, xua,
SCCP_RETURN_CAUSE_UNQUALIFIED);
} else
return scrc_translate_node_9(inst, xua, called);
}
/***********************************************************************
* Entrance points from MTP, SCLC, SCOC, ...
***********************************************************************/
/* Figure C.1/Q.714 - SCCP Routing control procedures (SCRC) */
/* Connection oriented message SCOC -> SCRC */
int sccp_scrc_rx_scoc_conn_msg(struct osmo_sccp_instance *inst,
struct xua_msg *xua)
{
struct osmo_sccp_addr called;
LOGP(DLSS7, LOGL_DEBUG, "%s: %s\n", __func__, xua_msg_dump(xua, &xua_dialect_sua));
sua_addr_parse(&called, xua, SUA_IEI_DEST_ADDR);
/* Is this a CR message ? */
if (xua->hdr.msg_type != SUA_CO_CORE)
return scrc_node_2(inst, xua, &called);
/* TOOD: Coupling performed (not supported) */
if (0) {
return scrc_node_2(inst, xua, &called);
}
return scrc_local_out_common(inst, xua, &called);
}
/* Connectionless Message SCLC -> SCRC */
int sccp_scrc_rx_sclc_msg(struct osmo_sccp_instance *inst,
struct xua_msg *xua)
{
struct osmo_sccp_addr called;
LOGP(DLSS7, LOGL_DEBUG, "%s: %s\n", __func__, xua_msg_dump(xua, &xua_dialect_sua));
sua_addr_parse(&called, xua, SUA_IEI_DEST_ADDR);
/* Message Type */
if (xua->hdr.msg_type == SUA_CL_CLDR) {
/* UDTS, XUDTS or LUDTS */
if (called.ri != OSMO_SCCP_RI_GT)
return scrc_node_7(inst, xua, &called);
/* Fall-through */
} else {
if (0 /* TODO: translation already performed */) {
/* Node 12 (Sheet 5) */
return scrc_node_12(inst, xua, &called);
}
}
return scrc_local_out_common(inst, xua, &called);
}
/* ensure the CallingParty address doesn't just contain SSN, but at least SSN+PC */
static void ensure_opc_in_calling_ssn(struct osmo_sccp_instance *inst,
struct xua_msg *xua)
{
struct osmo_sccp_addr calling;
sua_addr_parse(&calling, xua, SUA_IEI_SRC_ADDR);
/* if we route on SSN and only have a SSN in the address... */
if (calling.ri == OSMO_SCCP_RI_SSN_PC &&
calling.presence == OSMO_SCCP_ADDR_T_SSN) {
/* add the M3UA OPC to the address to ensure that the recipient
* can actually respond back to the source */
calling.presence |= OSMO_SCCP_ADDR_T_PC;
calling.pc = xua->mtp.opc;
xua_msg_free_tag(xua, SUA_IEI_SRC_ADDR);
xua_msg_add_sccp_addr(xua, SUA_IEI_SRC_ADDR, &calling);
}
}
/* Figure C.1/Q.714 Sheet 1 of 12, after we converted the
* MTP-TRANSFER.ind to SUA. */
int scrc_rx_mtp_xfer_ind_xua(struct osmo_sccp_instance *inst,
struct xua_msg *xua)
{
struct osmo_sccp_addr called;
uint32_t proto_class;
struct xua_msg_part *hop_ctr_part;
LOGP(DLSS7, LOGL_DEBUG, "%s: %s\n", __func__, xua_msg_dump(xua, &xua_dialect_sua));
/* TODO: SCCP or nodal congestion? */
/* CR or CL message? */
if (!sua_is_connectionless(xua) && !sua_is_cr(xua)) {
/* Node 1 (Sheet 3) */
/* deliver to SCOC */
sccp_scoc_rx_from_scrc(inst, xua);
return 0;
}
/* We only treat connectionless and CR below */
/* ensure we have at least OPC+SSN and not just SSN in CallingParty (OS#5146) */
ensure_opc_in_calling_ssn(inst, xua);
sua_addr_parse(&called, xua, SUA_IEI_DEST_ADDR);
/* Route on GT? */
if (called.ri != OSMO_SCCP_RI_GT) {
/* Node 6 (Sheet 3) */
return scrc_node_6(inst, xua, &called);
}
/* Message with hop-counter? */
hop_ctr_part = xua_msg_find_tag(xua, SUA_IEI_S7_HOP_CTR);
if (hop_ctr_part) {
uint32_t hop_counter = xua_msg_part_get_u32(hop_ctr_part);
if (hop_counter <= 1) {
/* Error: hop-counter violation */
/* node 4 */
return scrc_node_4(inst, xua, SCCP_RETURN_CAUSE_HOP_COUNTER_VIOLATION);
}
/* Decrement hop-counter */
hop_counter--;
*(uint32_t *)hop_ctr_part->dat = htonl(hop_counter);
}
/* node 3 (Sheet 2) */
/* Protocol class 0? */
proto_class = xua_msg_get_u32(xua, SUA_IEI_PROTO_CLASS);
switch (proto_class) {
case 0:
/* TODO: Assign SLS */
break;
case 1:
/* TODO: Map incoming SLS to outgoing SLS */
break;
default:
break;
}
return scrc_translate_node_9(inst, xua, &called);
}