aboutsummaryrefslogtreecommitdiffstats
path: root/src/sccp_sclc.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/sccp_sclc.c')
-rw-r--r--src/sccp_sclc.c337
1 files changed, 337 insertions, 0 deletions
diff --git a/src/sccp_sclc.c b/src/sccp_sclc.c
new file mode 100644
index 0000000..dae2c36
--- /dev/null
+++ b/src/sccp_sclc.c
@@ -0,0 +1,337 @@
+/* SCCP Connectionless Control (SCLC) according to ITU-T Q.713/Q.714 */
+
+/* (C) 2015-2017 by Harald Welte <laforge@gnumonks.org>
+ * All Rights reserved
+ *
+ * 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/>.
+ *
+ */
+
+/* This code is a bit of a hybrid between the ITU-T Q.71x specifications
+ * for SCCP (particularly its connection-oriented part), and the IETF
+ * RFC 3868 (SUA). The idea here is to have one shared code base of the
+ * state machines for SCCP Connection Oriented, and use those both from
+ * SCCP and SUA.
+ *
+ * To do so, all SCCP messages are translated to SUA messages in the
+ * input side, and all generated SUA messages are translated to SCCP on
+ * the output side.
+ *
+ * The Choice of going for SUA messages as the "native" format was based
+ * on their easier parseability, and the fact that there are features in
+ * SUA which classic SCCP cannot handle (like IP addresses in GT).
+ * However, all SCCP features can be expressed in SUA.
+ *
+ * The code only supports Class 2. No support for Class 3 is intended,
+ * but patches are of course alwys welcome.
+ *
+ * Missing other features:
+ * * Segmentation/Reassembly support
+ * * T(guard) after (re)start
+ * * freezing of local references
+ * * parsing/encoding of IPv4/IPv6 addresses
+ * * use of multiple Routing Contexts in SUA case
+ */
+
+#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 <sccp/sccp_types.h>
+
+#include "xua_internal.h"
+#include "sccp_internal.h"
+
+/* generate a 'struct xua_msg' of requested type from primitive data */
+static struct xua_msg *xua_gen_msg_cl(uint32_t event,
+ struct osmo_scu_prim *prim, int msg_type)
+{
+ struct xua_msg *xua = xua_msg_alloc();
+ struct osmo_scu_unitdata_param *udpar = &prim->u.unitdata;
+
+ if (!xua)
+ return NULL;
+
+ switch (msg_type) {
+ case SUA_CL_CLDT:
+ xua->hdr = XUA_HDR(SUA_MSGC_CL, SUA_CL_CLDT);
+ xua_msg_add_u32(xua, SUA_IEI_ROUTE_CTX, 0); /* FIXME */
+ xua_msg_add_u32(xua, SUA_IEI_PROTO_CLASS, 0);
+ xua_msg_add_sccp_addr(xua, SUA_IEI_SRC_ADDR, &udpar->calling_addr);
+ xua_msg_add_sccp_addr(xua, SUA_IEI_DEST_ADDR, &udpar->called_addr);
+ xua_msg_add_u32(xua, SUA_IEI_SEQ_CTRL, udpar->in_sequence_control);
+ /* optional: importance, ... correlation id? */
+ if (!prim)
+ goto prim_needed;
+ xua_msg_add_data(xua, SUA_IEI_DATA, msgb_l2len(prim->oph.msg),
+ msgb_l2(prim->oph.msg));
+ break;
+ default:
+ LOGP(DLSCCP, LOGL_ERROR, "Unknown msg_type %u\n", msg_type);
+ xua_msg_free(xua);
+ return NULL;
+ }
+ return xua;
+
+prim_needed:
+ xua_msg_free(xua);
+ LOGP(DLSCCP, LOGL_ERROR, "%s must be called with valid 'prim' "
+ "pointer for msg_type=%u\n", __func__, msg_type);
+ return NULL;
+}
+
+/* generate xua_msg, encode it and send it to SCRC */
+static int xua_gen_encode_and_send(struct osmo_sccp_user *scu, uint32_t event,
+ struct osmo_scu_prim *prim, int msg_type)
+{
+ struct xua_msg *xua;
+
+ xua = xua_gen_msg_cl(event, prim, msg_type);
+ if (!xua)
+ return -1;
+
+ return sccp_scrc_rx_sclc_msg(scu->inst, xua);
+}
+
+/*! \brief Main entrance function for primitives from SCCP User
+ * \param[in] scu SCCP User who is sending the primitive
+ * \param[on] oph Osmocom primitive header of the primitive
+ * \returns 0 on success; negtive in case of error */
+int sccp_sclc_user_sap_down(struct osmo_sccp_user *scu, struct osmo_prim_hdr *oph)
+{
+ struct osmo_scu_prim *prim = (struct osmo_scu_prim *) oph;
+ struct msgb *msg = prim->oph.msg;
+ int rc = 0;
+
+ /* we get called from osmo_sccp_user_sap_down() which already
+ * has debug-logged the primitive */
+
+ switch (OSMO_PRIM_HDR(&prim->oph)) {
+ case OSMO_PRIM(OSMO_SCU_PRIM_N_UNITDATA, PRIM_OP_REQUEST):
+ /* Connectionless by-passes this altogether */
+ rc = xua_gen_encode_and_send(scu, -1, prim, SUA_CL_CLDT);
+ goto out;
+ default:
+ LOGP(DLSCCP, LOGL_ERROR, "Received unknown SCCP User "
+ "primitive %s from user\n",
+ osmo_scu_prim_name(&prim->oph));
+ rc = -1;
+ goto out;
+ }
+
+out:
+ /* the SAP is supposed to consume the primitive/msgb */
+ msgb_free(msg);
+
+ return rc;
+}
+
+/* Process an incoming CLDT message (from a remote peer) */
+static int sclc_rx_cldt(struct osmo_sccp_instance *inst, struct xua_msg *xua)
+{
+ struct osmo_scu_prim *prim;
+ struct osmo_scu_unitdata_param *param;
+ struct xua_msg_part *data_ie = xua_msg_find_tag(xua, SUA_IEI_DATA);
+ struct msgb *upmsg = sccp_msgb_alloc(__func__);
+ struct osmo_sccp_user *scu;
+ uint32_t protocol_class;
+
+ /* fill primitive */
+ prim = (struct osmo_scu_prim *) msgb_put(upmsg, sizeof(*prim));
+ param = &prim->u.unitdata;
+ osmo_prim_init(&prim->oph, SCCP_SAP_USER,
+ OSMO_SCU_PRIM_N_UNITDATA,
+ PRIM_OP_INDICATION, upmsg);
+ sua_addr_parse(&param->called_addr, xua, SUA_IEI_DEST_ADDR);
+ sua_addr_parse(&param->calling_addr, xua, SUA_IEI_SRC_ADDR);
+ param->in_sequence_control = xua_msg_get_u32(xua, SUA_IEI_SEQ_CTRL);
+ protocol_class = xua_msg_get_u32(xua, SUA_IEI_PROTO_CLASS);
+ param->return_option = protocol_class & 0x80;
+ param->importance = xua_msg_get_u32(xua, SUA_IEI_IMPORTANCE);
+
+ scu = sccp_user_find(inst, param->called_addr.ssn,
+ param->called_addr.pc);
+
+ if (!scu) {
+ /* FIXME: Send destination unreachable? */
+ LOGP(DLSUA, LOGL_NOTICE, "Received SUA message for unequipped SSN %u\n",
+ param->called_addr.ssn);
+ msgb_free(upmsg);
+ return 0;
+ }
+
+ /* copy data */
+ upmsg->l2h = msgb_put(upmsg, data_ie->len);
+ memcpy(upmsg->l2h, data_ie->dat, data_ie->len);
+
+ /* send to user SAP */
+ sccp_user_prim_up(scu, prim);
+
+ /* xua_msg is free'd by our caller */
+ return 0;
+}
+
+static int sclc_rx_cldr(struct osmo_sccp_instance *inst, struct xua_msg *xua)
+{
+ struct osmo_scu_prim *prim;
+ struct osmo_scu_notice_param *param;
+ struct xua_msg_part *data_ie = xua_msg_find_tag(xua, SUA_IEI_DATA);
+ struct msgb *upmsg = sccp_msgb_alloc(__func__);
+ struct osmo_sccp_user *scu;
+
+ /* fill primitive */
+ prim = (struct osmo_scu_prim *) msgb_put(upmsg, sizeof(*prim));
+ param = &prim->u.notice;
+ osmo_prim_init(&prim->oph, SCCP_SAP_USER,
+ OSMO_SCU_PRIM_N_NOTICE,
+ PRIM_OP_INDICATION, upmsg);
+
+ sua_addr_parse(&param->called_addr, xua, SUA_IEI_DEST_ADDR);
+ sua_addr_parse(&param->calling_addr, xua, SUA_IEI_SRC_ADDR);
+ param->importance = xua_msg_get_u32(xua, SUA_IEI_IMPORTANCE);
+ param->cause = xua_msg_get_u32(xua, SUA_IEI_CAUSE);
+
+ scu = sccp_user_find(inst, param->called_addr.ssn,
+ param->called_addr.pc);
+ if (!scu) {
+ /* FIXME: Send destination unreachable? */
+ LOGP(DLSUA, LOGL_NOTICE, "Received CLDR for unequipped SSN %u\n",
+ param->called_addr.ssn);
+ msgb_free(upmsg);
+ return 0;
+ }
+
+ /* copy data */
+ upmsg->l2h = msgb_put(upmsg, data_ie->len);
+ memcpy(upmsg->l2h, data_ie->dat, data_ie->len);
+
+ /* send to user SAP */
+ sccp_user_prim_up(scu, prim);
+
+ /* xua_msg is free'd by our caller */
+ return 0;
+}
+
+/*! \brief SCRC -> SCLC (connectionless message)
+ * \param[in] inst SCCP Instance in which we operate
+ * \param[in] xua SUA connectionless message
+ * \returns 0 on success; negative on error */
+int sccp_sclc_rx_from_scrc(struct osmo_sccp_instance *inst,
+ struct xua_msg *xua)
+{
+ int rc = -1;
+
+ OSMO_ASSERT(xua->hdr.msg_class == SUA_MSGC_CL);
+
+ switch (xua->hdr.msg_type) {
+ case SUA_CL_CLDT:
+ rc = sclc_rx_cldt(inst, xua);
+ break;
+ case SUA_CL_CLDR:
+ rc = sclc_rx_cldr(inst, xua);
+ break;
+ default:
+ LOGP(DLSUA, LOGL_NOTICE, "Received unknown/unsupported "
+ "message %s\n", xua_hdr_dump(xua, &xua_dialect_sua));
+ break;
+ }
+
+ return rc;
+}
+
+/* generate a return/refusal message (SUA CLDR == SCCP UDTS) based on
+ * the incoming message. We need to flip all identities between sender
+ * and receiver */
+static struct xua_msg *gen_ret_msg(struct osmo_sccp_instance *inst,
+ const struct xua_msg *xua_in,
+ uint32_t ret_cause)
+{
+ struct xua_msg *xua_out = xua_msg_alloc();
+ struct osmo_sccp_addr called;
+
+ xua_out->hdr = XUA_HDR(SUA_MSGC_CL, SUA_CL_CLDR);
+ xua_msg_add_u32(xua_out, SUA_IEI_ROUTE_CTX, inst->route_ctx);
+ xua_msg_add_u32(xua_out, SUA_IEI_CAUSE,
+ SUA_CAUSE_T_RETURN | ret_cause);
+ /* Swap Calling and Called Party */
+ xua_msg_copy_part(xua_out, SUA_IEI_SRC_ADDR, xua_in, SUA_IEI_DEST_ADDR);
+ xua_msg_copy_part(xua_out, SUA_IEI_DEST_ADDR, xua_in, SUA_IEI_SRC_ADDR);
+ /* TODO: Optional: Hop Count */
+ /* Optional: Importance */
+ xua_msg_copy_part(xua_out, SUA_IEI_IMPORTANCE,
+ xua_in, SUA_IEI_IMPORTANCE);
+ /* Optional: Message Priority */
+ xua_msg_copy_part(xua_out, SUA_IEI_MSG_PRIO, xua_in, SUA_IEI_MSG_PRIO);
+ /* Optional: Correlation ID */
+ xua_msg_copy_part(xua_out, SUA_IEI_CORR_ID, xua_in, SUA_IEI_CORR_ID);
+ /* Optional: Segmentation */
+ xua_msg_copy_part(xua_out, SUA_IEI_SEGMENTATION,
+ xua_in, SUA_IEI_SEGMENTATION);
+ /* Optional: Data */
+ xua_msg_copy_part(xua_out, SUA_IEI_DATA, xua_in, SUA_IEI_DATA);
+
+ sua_addr_parse(&called, xua_out, SUA_IEI_DEST_ADDR);
+ /* Route on PC + SSN ? */
+ if (called.ri == OSMO_SCCP_RI_SSN_PC) {
+ /* if no PC, copy OPC into called addr */
+ if (!(called.presence & OSMO_SCCP_ADDR_T_PC)) {
+ struct osmo_sccp_addr calling;
+ sua_addr_parse(&calling, xua_out, SUA_IEI_SRC_ADDR);
+ called.presence |= OSMO_SCCP_ADDR_T_PC;
+ called.pc = calling.pc;
+ /* Re-encode / replace called address */
+ xua_msg_free_tag(xua_out, SUA_IEI_DEST_ADDR);
+ xua_msg_add_sccp_addr(xua_out, SUA_IEI_DEST_ADDR,
+ &called);
+ }
+ }
+ return xua_out;
+}
+
+/*! \brief SCRC -> SCLC (Routing Failure
+ * \param[in] inst SCCP Instance in which we operate
+ * \param[in] xua_in Message that failed to be routed
+ * \param[in] cause SCCP Return Cause */
+void sccp_sclc_rx_scrc_rout_fail(struct osmo_sccp_instance *inst,
+ struct xua_msg *xua_in, uint32_t cause)
+{
+ struct xua_msg *xua_out;
+
+ /* Figure C.12/Q.714 (Sheet 8) Node 9 */
+ switch (xua_in->hdr.msg_type) {
+ case SUA_CL_CLDT:
+ xua_out = gen_ret_msg(inst, xua_in, cause);
+ /* TODO: Message Return Option? */
+ if (!osmo_ss7_pc_is_local(inst->ss7, xua_in->mtp.opc)) {
+ /* non-local originator: send UDTS */
+ /* TODO: Assign SLS */
+ sccp_scrc_rx_sclc_msg(inst, xua_out);
+ } else {
+ /* local originator: send N-NOTICE to user */
+ /* TODO: N-NOTICE.ind SCLC -> SCU */
+ sclc_rx_cldr(inst, xua_out);
+ xua_msg_free(xua_out);
+ }
+ break;
+ case SUA_CL_CLDR:
+ /* do nothing */
+ break;
+ }
+}