/* SCCP Routing Control (SCRC) according to ITU-T Q.714 */ /* (C) 2015-2017 by Harald Welte * 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 . * */ #include #include #include #include #include #include #include #include #include #include "sccp_internal.h" #include "ss7_as.h" #include "ss7_instance.h" #include "ss7_linkset.h" #include "ss7_route.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); }