diff options
-rw-r--r-- | include/osmocom/sigtran/sccp_sap.h | 20 | ||||
-rw-r--r-- | src/Makefile.am | 3 | ||||
-rw-r--r-- | src/osmo_ss7.c | 2 | ||||
-rw-r--r-- | src/sccp_internal.h | 86 | ||||
-rw-r--r-- | src/sccp_sclc.c | 337 | ||||
-rw-r--r-- | src/sccp_scoc.c | 1642 | ||||
-rw-r--r-- | src/sccp_scrc.c | 473 | ||||
-rw-r--r-- | src/sccp_user.c | 377 |
8 files changed, 2938 insertions, 2 deletions
diff --git a/include/osmocom/sigtran/sccp_sap.h b/include/osmocom/sigtran/sccp_sap.h index 0cc1531..c1464f0 100644 --- a/include/osmocom/sigtran/sccp_sap.h +++ b/include/osmocom/sigtran/sccp_sap.h @@ -222,3 +222,23 @@ struct osmo_scu_prim { #define msgb_scu_prim(msg) ((struct osmo_scu_prim *)(msg)->l1h) char *osmo_scu_prim_name(struct osmo_prim_hdr *oph); + +struct osmo_ss7_instance; +struct osmo_sccp_instance; +struct osmo_sccp_user; + +struct osmo_sccp_instance * +osmo_sccp_instance_create(struct osmo_ss7_instance *ss7, void *priv); +void osmo_sccp_instance_destroy(struct osmo_sccp_instance *inst); + +void osmo_sccp_user_unbind(struct osmo_sccp_user *scu); + +struct osmo_sccp_user * +osmo_sccp_user_bind_pc(struct osmo_sccp_instance *inst, const char *name, + osmo_prim_cb prim_cb, uint16_t ssn, uint32_t pc); + +struct osmo_sccp_user * +osmo_sccp_user_bind(struct osmo_sccp_instance *inst, const char *name, + osmo_prim_cb prim_cb, uint16_t ssn); + +int osmo_sccp_user_sap_down(struct osmo_sccp_user *scu, struct osmo_prim_hdr *oph); diff --git a/src/Makefile.am b/src/Makefile.am index 4455127..a4cfeeb 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -27,7 +27,8 @@ lib_LTLIBRARIES = libosmo-sigtran.la LIBVERSION=0:0:0 libosmo_sigtran_la_SOURCES = sccp_sap.c sua.c m3ua.c xua_msg.c sccp_helpers.c \ - sccp2sua.c \ + sccp2sua.c sccp_scrc.c sccp_sclc.c sccp_scoc.c \ + sccp_user.c \ osmo_ss7.c osmo_ss7_hmrt.c xua_asp_fsm.c xua_as_fsm.c libosmo_sigtran_la_LDFLAGS = -version-info $(LIBVERSION) -no-undefined -export-symbols-regex '^osmo_' libosmo_sigtran_la_LIBADD = $(LIBOSMOCORE_LIBS) $(LIBOSMONETIF_LIBS) $(LIBSCTP_LIBS) diff --git a/src/osmo_ss7.c b/src/osmo_ss7.c index 74c54bb..6d0b446 100644 --- a/src/osmo_ss7.c +++ b/src/osmo_ss7.c @@ -41,6 +41,7 @@ #include <osmocom/netif/stream.h> +#include "sccp_internal.h" #include "xua_internal.h" #include "xua_asp_fsm.h" #include "xua_as_fsm.h" @@ -1483,6 +1484,7 @@ int osmo_ss7_init(void) { if (ss7_initialized) return 1; + osmo_fsm_register(&sccp_scoc_fsm); osmo_fsm_register(&xua_as_fsm); osmo_fsm_register(&xua_asp_fsm); ss7_initialized = true; diff --git a/src/sccp_internal.h b/src/sccp_internal.h index 7287a82..c35ef4b 100644 --- a/src/sccp_internal.h +++ b/src/sccp_internal.h @@ -1,5 +1,89 @@ #pragma once -#include <osmocom/core/msgb.h> +#include <osmocom/core/fsm.h> +#include <osmocom/core/prim.h> +#include <osmocom/sigtran/sccp_sap.h> +#include <osmocom/sigtran/osmo_ss7.h> + +/* an instance of the SCCP stack */ +struct osmo_sccp_instance { + /* entry in global list of ss7 instances */ + struct llist_head list; + /* list of 'struct sccp_connection' in this instance */ + struct llist_head connections; + /* list of SCCP users in this instance */ + struct llist_head users; + /* routing context to be used in all outbound messages */ + uint32_t route_ctx; + /* next local reference to allocate */ + uint32_t next_id; + struct osmo_ss7_instance *ss7; + void *priv; + + struct osmo_ss7_user ss7_user; +}; + +struct osmo_sccp_user { + /*! \brief entry in list of sccp users of \ref osmo_sccp_instance */ + struct llist_head list; + /*! \brief pointer back to SCCP instance */ + struct osmo_sccp_instance *inst; + /*! \brief human-readable name of this user */ + char *name; + + /*! \brief SSN and/or point code to which we are bound */ + uint16_t ssn; + uint32_t pc; + bool pc_valid; + + /* set if we are a server */ + struct llist_head links; + + /* user call-back function in case of incoming primitives */ + osmo_prim_cb prim_cb; + void *priv; + + /* Application Server FSM Instance */ + struct osmo_fsm_inst *as_fi; +}; + +extern int DSCCP; + +struct xua_msg; + +struct osmo_sccp_user * +sccp_user_find(struct osmo_sccp_instance *inst, uint16_t ssn, uint32_t pc); + +/* Message from SCOC -> SCRC */ +int sccp_scrc_rx_scoc_conn_msg(struct osmo_sccp_instance *inst, + struct xua_msg *xua); + +/* Message from SCLC -> SCRC */ +int sccp_scrc_rx_sclc_msg(struct osmo_sccp_instance *inst, struct xua_msg *xua); + +/* Message from MTP (SUA) -> SCRC */ +int scrc_rx_mtp_xfer_ind_xua(struct osmo_sccp_instance *inst, + struct xua_msg *xua); + +/* Message from SCRC -> SCOC */ +void sccp_scoc_rx_from_scrc(struct osmo_sccp_instance *inst, + struct xua_msg *xua); +void sccp_scoc_rx_scrc_rout_fail(struct osmo_sccp_instance *inst, + struct xua_msg *xua, uint32_t cause); + +void sccp_scoc_flush_connections(struct osmo_sccp_instance *inst); + +/* Message from SCRC -> SCLC */ +int sccp_sclc_rx_from_scrc(struct osmo_sccp_instance *inst, + struct xua_msg *xua); +void sccp_sclc_rx_scrc_rout_fail(struct osmo_sccp_instance *inst, + struct xua_msg *xua, uint32_t cause); + +int sccp_user_prim_up(struct osmo_sccp_user *scut, struct osmo_scu_prim *prim); + +/* SCU -> SCLC */ +int sccp_sclc_user_sap_down(struct osmo_sccp_user *scu, struct osmo_prim_hdr *oph); struct msgb *sccp_msgb_alloc(const char *name); + +struct osmo_fsm sccp_scoc_fsm; 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(¶m->called_addr, xua, SUA_IEI_DEST_ADDR); + sua_addr_parse(¶m->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(¶m->called_addr, xua, SUA_IEI_DEST_ADDR); + sua_addr_parse(¶m->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; + } +} diff --git a/src/sccp_scoc.c b/src/sccp_scoc.c new file mode 100644 index 0000000..f881872 --- /dev/null +++ b/src/sccp_scoc.c @@ -0,0 +1,1642 @@ +/* SCCP Connection Oriented (SCOC) 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" + +#define S(x) (1 << (x)) +#define SCU_MSGB_SIZE 1024 + +/* Appendix C.4 of Q.714 (all in milliseconds) */ +#define CONNECTION_TIMER ( 1 * 60 * 100) +#define TX_INACT_TIMER ( 7 * 60 * 100) /* RFC 3868 Ch. 8. */ +#define RX_INACT_TIMER (15 * 60 * 100) /* RFC 3868 Ch. 8. */ +#define RELEASE_TIMER ( 10 * 100) +#define RELEASE_REP_TIMER ( 10 * 100) +#define INT_TIMER ( 1 * 60 * 100) +#define GUARD_TIMER (23 * 60 * 100) +#define RESET_TIMER ( 10 * 100) + +/* convert from single value in milliseconds to comma-separated + * "seconds, microseconds" format we use in osmocom/core/timers.h */ +#define MSEC_TO_S_US(x) (x/100), ((x%100)*10) + +/*********************************************************************** + * SCCP connection table + ***********************************************************************/ + +/* a logical connection within the SCCP instance */ +struct sccp_connection { + /* part of osmo_sccp_instance.list */ + struct llist_head list; + /* which instance are we part of? */ + struct osmo_sccp_instance *inst; + /* which user owns us? */ + struct osmo_sccp_user *user; + + /* remote point code */ + uint32_t remote_pc; + + /* local/remote addresses and identiies */ + struct osmo_sccp_addr calling_addr; + struct osmo_sccp_addr called_addr; + uint32_t conn_id; + uint32_t remote_ref; + + uint32_t importance; + uint32_t sccp_class; + uint32_t release_cause; /* WAIT_CONN_CONF */ + + /* Osmo FSM Instance of sccp_scoc_fsm */ + struct osmo_fsm_inst *fi; + + /* Connect timer */ + struct osmo_timer_list t_conn; + + /* inactivity timers */ + struct osmo_timer_list t_ias; + struct osmo_timer_list t_iar; + + /* release timers */ + struct osmo_timer_list t_rel; + struct osmo_timer_list t_int; + struct osmo_timer_list t_rep_rel; +}; + +/*********************************************************************** + * various helper functions + ***********************************************************************/ + +enum sccp_connection_state { + S_IDLE, + S_CONN_PEND_IN, + S_CONN_PEND_OUT, + S_ACTIVE, + S_DISCONN_PEND, + S_RESET_IN, + S_RESET_OUT, + S_BOTHWAY_RESET, + S_WAIT_CONN_CONF, +}; + +/* Events that this FSM can process */ +enum sccp_scoc_event { + /* Primitives from SCCP-User */ + SCOC_E_SCU_N_CONN_REQ, + SCOC_E_SCU_N_CONN_RESP, + SCOC_E_SCU_N_DISC_REQ, + SCOC_E_SCU_N_DATA_REQ, + SCOC_E_SCU_N_EXP_DATA_REQ, + + /* Events from RCOC (Routing for Connection Oriented) */ + SCOC_E_RCOC_CONN_IND, + SCOC_E_RCOC_ROUT_FAIL_IND, + SCOC_E_RCOC_RLSD_IND, + SCOC_E_RCOC_REL_COMPL_IND, + SCOC_E_RCOC_CREF_IND, + SCOC_E_RCOC_CC_IND, + SCOC_E_RCOC_DT1_IND, + SCOC_E_RCOC_DT2_IND, + SCOC_E_RCOC_IT_IND, + SCOC_E_RCOC_OTHER_NPDU, + SCOC_E_RCOC_ERROR_IND, + + /* Timer Events */ + SCOC_E_T_IAR_EXP, + SCOC_E_T_IAS_EXP, + + SCOC_E_CONN_TMR_EXP, + + SCOC_E_T_REL_EXP, + SCOC_E_T_INT_EXP, + SCOC_E_T_REP_REL_EXP, +}; + +static const struct value_string scoc_event_names[] = { + /* Primitives from SCCP-User */ + { SCOC_E_SCU_N_CONN_REQ, "N-CONNECT.req" }, + { SCOC_E_SCU_N_CONN_RESP, "N-CONNECT.resp" }, + { SCOC_E_SCU_N_DISC_REQ, "N-DISCONNECT.req" }, + { SCOC_E_SCU_N_DATA_REQ, "N-DATA.req" }, + { SCOC_E_SCU_N_EXP_DATA_REQ, "N-EXPEDITED_DATA.req" }, + + /* Events from RCOC (Routing for Connection Oriented) */ + { SCOC_E_RCOC_CONN_IND, "RCOC-CONNECT.ind" }, + { SCOC_E_RCOC_ROUT_FAIL_IND, "RCOC-ROUT_FAIL.ind" }, + { SCOC_E_RCOC_RLSD_IND, "RCOC-RELEASED.ind" }, + { SCOC_E_RCOC_REL_COMPL_IND, "RCOC-RELEASE_COMPLETE.ind" }, + { SCOC_E_RCOC_CREF_IND, "RCOC-CONNECT_REFUSED.ind" }, + { SCOC_E_RCOC_CC_IND, "RCOC-CONNECT_CONFIRM.ind" }, + { SCOC_E_RCOC_DT1_IND, "RCOC-DT1.ind" }, + { SCOC_E_RCOC_DT2_IND, "RCOC-DT2.ind" }, + { SCOC_E_RCOC_IT_IND, "RCOC-IT.ind" }, + { SCOC_E_RCOC_OTHER_NPDU, "RCOC-OTHER_NPDU.ind" }, + { SCOC_E_RCOC_ERROR_IND, "RCOC-ERROR.ind" }, + + { SCOC_E_T_IAR_EXP, "T(iar)_expired" }, + { SCOC_E_T_IAS_EXP, "T(ias)_expired" }, + { SCOC_E_CONN_TMR_EXP, "T(conn)_expired" }, + { SCOC_E_T_REL_EXP, "T(rel)_expired" }, + { SCOC_E_T_INT_EXP, "T(int)_expired" }, + { SCOC_E_T_REP_REL_EXP, "T(rep_rel)_expired" }, + + { 0, NULL } +}; + +/* how to map a SCCP CO message to an event */ +static const struct xua_msg_event_map sua_scoc_event_map[] = { + { SUA_MSGC_CO, SUA_CO_CORE, SCOC_E_RCOC_CONN_IND }, + { SUA_MSGC_CO, SUA_CO_RELRE, SCOC_E_RCOC_RLSD_IND }, + { SUA_MSGC_CO, SUA_CO_RELCO, SCOC_E_RCOC_REL_COMPL_IND }, + { SUA_MSGC_CO, SUA_CO_COREF, SCOC_E_RCOC_CREF_IND }, + { SUA_MSGC_CO, SUA_CO_COAK, SCOC_E_RCOC_CC_IND }, + { SUA_MSGC_CO, SUA_CO_CODT, SCOC_E_RCOC_DT1_IND }, + { SUA_MSGC_CO, SUA_CO_COIT, SCOC_E_RCOC_IT_IND }, + { SUA_MSGC_CO, SUA_CO_COERR, SCOC_E_RCOC_ERROR_IND }, +}; + + +/* map from SCU-primitives to SCOC FSM events */ +static const struct osmo_prim_event_map scu_scoc_event_map[] = { + { SCCP_SAP_USER, OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_REQUEST, + SCOC_E_SCU_N_CONN_REQ }, + { SCCP_SAP_USER, OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_RESPONSE, + SCOC_E_SCU_N_CONN_RESP }, + { SCCP_SAP_USER, OSMO_SCU_PRIM_N_DATA, PRIM_OP_REQUEST, + SCOC_E_SCU_N_DATA_REQ }, + { SCCP_SAP_USER, OSMO_SCU_PRIM_N_DISCONNECT, PRIM_OP_REQUEST, + SCOC_E_SCU_N_DISC_REQ }, + { SCCP_SAP_USER, OSMO_SCU_PRIM_N_EXPEDITED_DATA, PRIM_OP_REQUEST, + SCOC_E_SCU_N_EXP_DATA_REQ }, + { 0, 0, 0, OSMO_NO_EVENT } +}; + +/*********************************************************************** + * Timer Handling + ***********************************************************************/ + +/* T(ias) has expired, send a COIT message to the peer */ +static void tx_inact_tmr_cb(void *data) +{ + struct sccp_connection *conn = data; + osmo_fsm_inst_dispatch(conn->fi, SCOC_E_T_IAS_EXP, NULL); +} + +/* T(iar) has expired, notify the FSM about it */ +static void rx_inact_tmr_cb(void *data) +{ + struct sccp_connection *conn = data; + osmo_fsm_inst_dispatch(conn->fi, SCOC_E_T_IAR_EXP, NULL); +} + +/* T(rel) has expired, notify the FSM about it */ +static void rel_tmr_cb(void *data) +{ + struct sccp_connection *conn = data; + osmo_fsm_inst_dispatch(conn->fi, SCOC_E_T_REL_EXP, NULL); +} + +/* T(int) has expired, notify the FSM about it */ +static void int_tmr_cb(void *data) +{ + struct sccp_connection *conn = data; + osmo_fsm_inst_dispatch(conn->fi, SCOC_E_T_INT_EXP, NULL); +} + +/* T(repeat_rel) has expired, notify the FSM about it */ +static void rep_rel_tmr_cb(void *data) +{ + struct sccp_connection *conn = data; + osmo_fsm_inst_dispatch(conn->fi, SCOC_E_T_REP_REL_EXP, NULL); +} + +/* T(conn) has expired, notify the FSM about it */ +static void conn_tmr_cb(void *data) +{ + struct sccp_connection *conn = data; + osmo_fsm_inst_dispatch(conn->fi, SCOC_E_CONN_TMR_EXP, NULL); +} + +/* Re-start the Tx inactivity timer */ +static void conn_restart_tx_inact_timer(struct sccp_connection *conn) +{ + osmo_timer_schedule(&conn->t_ias, MSEC_TO_S_US(TX_INACT_TIMER)); +} + +/* Re-start the Rx inactivity timer */ +static void conn_restart_rx_inact_timer(struct sccp_connection *conn) +{ + osmo_timer_schedule(&conn->t_iar, MSEC_TO_S_US(RX_INACT_TIMER)); +} + +/* Re-start both Rx and Tx inactivity timers */ +static void conn_start_inact_timers(struct sccp_connection *conn) +{ + conn_restart_tx_inact_timer(conn); + conn_restart_rx_inact_timer(conn); +} + +/* Stop both Rx and Tx inactivity timers */ +static void conn_stop_inact_timers(struct sccp_connection *conn) +{ + osmo_timer_del(&conn->t_ias); + osmo_timer_del(&conn->t_iar); +} + +/* Start release timer T(rel) */ +static void conn_start_rel_timer(struct sccp_connection *conn) +{ + osmo_timer_schedule(&conn->t_rel, MSEC_TO_S_US(RELEASE_TIMER)); +} + +/* Start repeat release timer T(rep_rel) */ +static void conn_start_rep_rel_timer(struct sccp_connection *conn) +{ + osmo_timer_schedule(&conn->t_rep_rel, MSEC_TO_S_US(RELEASE_REP_TIMER)); +} + +/* Start interval timer T(int) */ +static void conn_start_int_timer(struct sccp_connection *conn) +{ + osmo_timer_schedule(&conn->t_int, MSEC_TO_S_US(INT_TIMER)); +} + +/* Stop all release related timers: T(rel), T(int) and T(rep_rel) */ +static void conn_stop_release_timers(struct sccp_connection *conn) +{ + osmo_timer_del(&conn->t_rel); + osmo_timer_del(&conn->t_int); + osmo_timer_del(&conn->t_rep_rel); +} + +/* Start connect timer T(conn) */ +static void conn_start_connect_timer(struct sccp_connection *conn) +{ + osmo_timer_schedule(&conn->t_conn, MSEC_TO_S_US(CONNECTION_TIMER)); +} + +/* Stop connect timer T(conn) */ +static void conn_stop_connect_timer(struct sccp_connection *conn) +{ + osmo_timer_del(&conn->t_conn); +} + + +/*********************************************************************** + * SUA Instance and Connection handling + ***********************************************************************/ + +static void conn_destroy(struct sccp_connection *conn); + +static struct sccp_connection *conn_find_by_id(struct osmo_sccp_instance *inst, uint32_t id) +{ + struct sccp_connection *conn; + + llist_for_each_entry(conn, &inst->connections, list) { + if (conn->conn_id == id) + return conn; + } + return NULL; +} + +#define INIT_TIMER(x, fn, priv) do { (x)->cb = fn; (x)->data = priv; } while (0) + +/* allocate + init a SCCP Connection with given ID (local reference) */ +static struct sccp_connection *conn_create_id(struct osmo_sccp_instance *inst, + uint32_t conn_id) +{ + struct sccp_connection *conn = talloc_zero(inst, struct sccp_connection); + char name[16]; + + conn->conn_id = conn_id; + conn->inst = inst; + + llist_add_tail(&conn->list, &inst->connections); + + INIT_TIMER(&conn->t_conn, conn_tmr_cb, conn); + INIT_TIMER(&conn->t_ias, tx_inact_tmr_cb, conn); + INIT_TIMER(&conn->t_iar, rx_inact_tmr_cb, conn); + INIT_TIMER(&conn->t_rel, rel_tmr_cb, conn); + INIT_TIMER(&conn->t_int, int_tmr_cb, conn); + INIT_TIMER(&conn->t_rep_rel, rep_rel_tmr_cb, conn); + + /* this might change at runtime, as it is not a constant :/ */ + sccp_scoc_fsm.log_subsys = DLSCCP; + + /* we simply use the local reference as FSM instance name */ + snprintf(name, sizeof(name), "%u", conn->conn_id); + conn->fi = osmo_fsm_inst_alloc(&sccp_scoc_fsm, conn, conn, + LOGL_DEBUG, name); + if (!conn->fi) { + llist_del(&conn->list); + talloc_free(conn); + return NULL; + } + + return conn; +} + +/* Search for next free connection ID (local reference) and allocate conn */ +static struct sccp_connection *conn_create(struct osmo_sccp_instance *inst) +{ + uint32_t conn_id; + + do { + conn_id = inst->next_id++; + } while (conn_find_by_id(inst, conn_id)); + + return conn_create_id(inst, conn_id); +} + +/* destroy a SCCP connection state, releasing all timers, terminating + * FSM and releasing associated memory */ +static void conn_destroy(struct sccp_connection *conn) +{ + conn_stop_connect_timer(conn); + conn_stop_inact_timers(conn); + conn_stop_release_timers(conn); + llist_del(&conn->list); + + osmo_fsm_inst_term(conn->fi, OSMO_FSM_TERM_REQUEST, NULL); + + talloc_free(conn); +} + +/* allocate a message buffer for an SCCP User Primitive */ +static struct msgb *scu_msgb_alloc(void) +{ + return msgb_alloc(SCU_MSGB_SIZE, "SCCP User Primitive"); +} + +/* generate a RELRE (release request) xua_msg for given conn */ +static struct xua_msg *xua_gen_relre(struct sccp_connection *conn, + uint32_t cause, + struct osmo_scu_prim *prim) +{ + struct xua_msg *xua = xua_msg_alloc(); + + if (!xua) + return NULL; + + xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_RELRE); + xua_msg_add_u32(xua, SUA_IEI_ROUTE_CTX, conn->inst->route_ctx); + xua_msg_add_u32(xua, SUA_IEI_DEST_REF, conn->remote_ref); + xua_msg_add_u32(xua, SUA_IEI_SRC_REF, conn->conn_id); + xua_msg_add_u32(xua, SUA_IEI_CAUSE, SUA_CAUSE_T_RELEASE | cause); + /* optional: importance */ + if (prim && msgb_l2(prim->oph.msg)) + xua_msg_add_data(xua, SUA_IEI_DATA, msgb_l2len(prim->oph.msg), + msgb_l2(prim->oph.msg)); + + return xua; +} + +/* generate xua_msg, encode it and send it to SCRC */ +static int xua_gen_relre_and_send(struct sccp_connection *conn, uint32_t cause, + struct osmo_scu_prim *prim) +{ + struct xua_msg *xua; + + xua = xua_gen_relre(conn, cause, prim); + if (!xua) + return -1; + + /* amend this with point code information; The SUA RELRE + * includes neither called nor calling party address! */ + xua->mtp.dpc = conn->remote_pc; + return sccp_scrc_rx_scoc_conn_msg(conn->inst, xua); +} + +/* generate a 'struct xua_msg' of requested type from connection + + * primitive data */ +static struct xua_msg *xua_gen_msg_co(struct sccp_connection *conn, uint32_t event, + struct osmo_scu_prim *prim, int msg_type) +{ + struct xua_msg *xua = xua_msg_alloc(); + + if (!xua) + return NULL; + + switch (msg_type) { + case SUA_CO_CORE: /* Connect Request == SCCP CR */ + xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_CORE); + xua_msg_add_u32(xua, SUA_IEI_ROUTE_CTX, conn->inst->route_ctx); + xua_msg_add_u32(xua, SUA_IEI_PROTO_CLASS, conn->sccp_class); + xua_msg_add_u32(xua, SUA_IEI_SRC_REF, conn->conn_id); + xua_msg_add_sccp_addr(xua, SUA_IEI_DEST_ADDR, &conn->called_addr); + xua_msg_add_u32(xua, SUA_IEI_SEQ_CTRL, 0); /* TODO */ + /* optional: sequence number (class 3 only) */ + if (conn->calling_addr.presence) + xua_msg_add_sccp_addr(xua, SUA_IEI_SRC_ADDR, &conn->calling_addr); + /* optional: hop count; importance; priority; credit */ + if (prim && msgb_l2(prim->oph.msg)) + xua_msg_add_data(xua, SUA_IEI_DATA, msgb_l2len(prim->oph.msg), + msgb_l2(prim->oph.msg)); + break; + case SUA_CO_COAK: /* Connect Acknowledge == SCCP CC */ + xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_COAK); + xua_msg_add_u32(xua, SUA_IEI_ROUTE_CTX, conn->inst->route_ctx); + xua_msg_add_u32(xua, SUA_IEI_PROTO_CLASS, conn->sccp_class); + xua_msg_add_u32(xua, SUA_IEI_DEST_REF, conn->remote_ref); + xua_msg_add_u32(xua, SUA_IEI_SRC_REF, conn->conn_id); + xua_msg_add_u32(xua, SUA_IEI_SEQ_CTRL, 0); /* TODO */ + /* optional: sequence number (class 3 only) */ + if (conn->called_addr.presence) + xua_msg_add_sccp_addr(xua, SUA_IEI_SRC_ADDR, &conn->called_addr); + /* optional: hop count; importance; priority */ + /* FIXME: destination address will [only] be present in + * case the CORE message conveys the source address + * parameter */ + if (conn->calling_addr.presence) + xua_msg_add_sccp_addr(xua, SUA_IEI_DEST_ADDR, &conn->calling_addr); + if (prim && msgb_l2(prim->oph.msg)) + xua_msg_add_data(xua, SUA_IEI_DATA, msgb_l2len(prim->oph.msg), + msgb_l2(prim->oph.msg)); + break; + case SUA_CO_RELRE: /* Release Request == SCCP REL */ + if (!prim) + goto prim_needed; + xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_RELRE); + xua_msg_add_u32(xua, SUA_IEI_ROUTE_CTX, conn->inst->route_ctx); + xua_msg_add_u32(xua, SUA_IEI_DEST_REF, conn->remote_ref); + xua_msg_add_u32(xua, SUA_IEI_SRC_REF, conn->conn_id); + xua_msg_add_u32(xua, SUA_IEI_CAUSE, SUA_CAUSE_T_RELEASE | prim->u.disconnect.cause); + /* optional: importance */ + if (msgb_l2(prim->oph.msg)) + xua_msg_add_data(xua, SUA_IEI_DATA, msgb_l2len(prim->oph.msg), + msgb_l2(prim->oph.msg)); + break; + case SUA_CO_RELCO: /* Release Confirm == SCCP RLSD */ + xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_RELCO); + xua_msg_add_u32(xua, SUA_IEI_ROUTE_CTX, conn->inst->route_ctx); + xua_msg_add_u32(xua, SUA_IEI_DEST_REF, conn->remote_ref); + xua_msg_add_u32(xua, SUA_IEI_SRC_REF, conn->conn_id); + /* optional: importance */ + break; + case SUA_CO_CODT: /* Connection Oriented Data Transfer == SCCP DT1 */ + if (!prim) + goto prim_needed; + xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_CODT); + xua_msg_add_u32(xua, SUA_IEI_ROUTE_CTX, conn->inst->route_ctx); + /* Sequence number only in expedited data */ + xua_msg_add_u32(xua, SUA_IEI_DEST_REF, conn->remote_ref); + /* optional: priority; correlation id */ + xua_msg_add_data(xua, SUA_IEI_DATA, msgb_l2len(prim->oph.msg), + msgb_l2(prim->oph.msg)); + break; + case SUA_CO_COIT: /* Connection Oriented Interval Timer == SCCP IT */ + xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_COIT); + xua_msg_add_u32(xua, SUA_IEI_ROUTE_CTX, conn->inst->route_ctx); + xua_msg_add_u32(xua, SUA_IEI_PROTO_CLASS, conn->sccp_class); + xua_msg_add_u32(xua, SUA_IEI_SRC_REF, conn->conn_id); + xua_msg_add_u32(xua, SUA_IEI_DEST_REF, conn->remote_ref); + /* optional: sequence number; credit (both class 3 only) */ + break; + case SUA_CO_COREF: /* Connect Refuse == SCCP CREF */ + xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_COREF); + xua_msg_add_u32(xua, SUA_IEI_ROUTE_CTX, conn->inst->route_ctx); + xua_msg_add_u32(xua, SUA_IEI_DEST_REF, conn->remote_ref); + //xua_msg_add_u32(xua, SUA_IEI_CAUSE, SUA_CAUSE_T_REFUSAL | prim->u.disconnect.cause); + xua_msg_add_u32(xua, SUA_IEI_CAUSE, SUA_CAUSE_T_REFUSAL | SCCP_REFUSAL_UNEQUIPPED_USER); + /* optional: source addr */ + if (conn->called_addr.presence) + xua_msg_add_sccp_addr(xua, SUA_IEI_SRC_ADDR, &conn->called_addr); + /* conditional: dest addr */ + if (conn->calling_addr.presence) + xua_msg_add_sccp_addr(xua, SUA_IEI_DEST_ADDR, &conn->calling_addr); + /* optional: importance */ + /* optional: data */ + if (prim && msgb_l2(prim->oph.msg)) + xua_msg_add_data(xua, SUA_IEI_DATA, msgb_l2len(prim->oph.msg), + msgb_l2(prim->oph.msg)); + break; + /* FIXME */ + default: + LOGP(DLSCCP, LOGL_ERROR, "Don't know how to encode 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 sccp_connection *conn, uint32_t event, + struct osmo_scu_prim *prim, int msg_type) +{ + struct xua_msg *xua; + + xua = xua_gen_msg_co(conn, event, prim, msg_type); + if (!xua) + return -1; + + /* amend this with point code information; Many CO msgs + * includes neither called nor calling party address! */ + xua->mtp.dpc = conn->remote_pc; + return sccp_scrc_rx_scoc_conn_msg(conn->inst, xua); +} + +/* allocate a SCU primitive to be sent to the user */ +static struct osmo_scu_prim *scu_prim_alloc(unsigned int primitive, enum osmo_prim_operation operation) +{ + struct msgb *upmsg = scu_msgb_alloc(); + struct osmo_scu_prim *prim; + + prim = (struct osmo_scu_prim *) msgb_put(upmsg, sizeof(*prim)); + osmo_prim_init(&prim->oph, SCCP_SAP_USER, + primitive, operation, upmsg); + return prim; +} + +/* high-level function to generate a SCCP User primitive of requested + * type based on the connection and currently processed XUA message */ +static void scu_gen_encode_and_send(struct sccp_connection *conn, uint32_t event, + struct xua_msg *xua, unsigned int primitive, + enum osmo_prim_operation operation) +{ + struct osmo_scu_prim *scu_prim; + struct osmo_scu_disconn_param *udisp; + struct osmo_scu_connect_param *uconp; + struct osmo_scu_data_param *udatp; + struct xua_msg_part *data_ie; + + scu_prim = scu_prim_alloc(primitive, operation); + + switch (OSMO_PRIM_HDR(&scu_prim->oph)) { + case OSMO_PRIM(OSMO_SCU_PRIM_N_DISCONNECT, PRIM_OP_INDICATION): + udisp = &scu_prim->u.disconnect; + udisp->conn_id = conn->conn_id; + udisp->responding_addr = conn->called_addr; + udisp->originator = OSMO_SCCP_ORIG_UNDEFINED; + //udisp->in_sequence_control; + if (xua) { + udisp->cause = xua_msg_get_u32(xua, SUA_IEI_CAUSE); + if (xua_msg_find_tag(xua, SUA_IEI_SRC_ADDR)) + sua_addr_parse(&udisp->responding_addr, xua, SUA_IEI_SRC_ADDR); + data_ie = xua_msg_find_tag(xua, SUA_IEI_DATA); + udisp->importance = xua_msg_get_u32(xua, SUA_IEI_IMPORTANCE); + if (data_ie) { + struct msgb *upmsg = scu_prim->oph.msg; + upmsg->l2h = msgb_put(upmsg, data_ie->len); + memcpy(upmsg->l2h, data_ie->dat, data_ie->len); + } + } + break; + case OSMO_PRIM(OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_INDICATION): + uconp = &scu_prim->u.connect; + uconp->conn_id = conn->conn_id; + uconp->called_addr = conn->called_addr; + uconp->calling_addr = conn->calling_addr; + uconp->sccp_class = conn->sccp_class; + uconp->importance = conn->importance; + data_ie = xua_msg_find_tag(xua, SUA_IEI_DATA); + if (xua) { + if (data_ie) { + struct msgb *upmsg = scu_prim->oph.msg; + upmsg->l2h = msgb_put(upmsg, data_ie->len); + memcpy(upmsg->l2h, data_ie->dat, data_ie->len); + } + } + break; + case OSMO_PRIM(OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_CONFIRM): + uconp = &scu_prim->u.connect; + uconp->conn_id = conn->conn_id; + uconp->called_addr = conn->called_addr; + uconp->calling_addr = conn->calling_addr; + //scu_prim->u.connect.in_sequence_control + uconp->sccp_class = xua_msg_get_u32(xua, SUA_IEI_PROTO_CLASS) & 3; + uconp->importance = xua_msg_get_u32(xua, SUA_IEI_IMPORTANCE); + data_ie = xua_msg_find_tag(xua, SUA_IEI_DATA); + if (data_ie) { + struct msgb *upmsg = scu_prim->oph.msg; + upmsg->l2h = msgb_put(upmsg, data_ie->len); + memcpy(upmsg->l2h, data_ie->dat, data_ie->len); + } + break; + case OSMO_PRIM(OSMO_SCU_PRIM_N_DATA, PRIM_OP_INDICATION): + udatp = &scu_prim->u.data; + udatp->conn_id = conn->conn_id; + udatp->importance = conn->importance; + data_ie = xua_msg_find_tag(xua, SUA_IEI_DATA); + if (data_ie) { + struct msgb *upmsg = scu_prim->oph.msg; + upmsg->l2h = msgb_put(upmsg, data_ie->len); + memcpy(upmsg->l2h, data_ie->dat, data_ie->len); + } + break; + default: + LOGPFSML(conn->fi, LOGL_ERROR, "Unsupported primitive %u:%u\n", + scu_prim->oph.primitive, scu_prim->oph.operation); + talloc_free(scu_prim->oph.msg); + return; + } + + sccp_user_prim_up(conn->user, scu_prim); +} + + +/*********************************************************************** + * Actual SCCP Connection Oriented Control (SCOC) Finite Stte Machine + ***********************************************************************/ + +/* Figure C.2/Q.714 (sheet 1 of 7) and C.3/Q.714 (sheet 1 of 6) */ +static void scoc_fsm_idle(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct sccp_connection *conn = fi->priv; + struct osmo_scu_prim *prim = NULL; + struct osmo_scu_connect_param *uconp; + struct xua_msg *xua = NULL; + + switch (event) { + case SCOC_E_SCU_N_CONN_REQ: + prim = data; + uconp = &prim->u.connect; + /* copy relevant parameters from prim to conn */ + conn->called_addr = uconp->called_addr; + conn->calling_addr = uconp->calling_addr; + conn->sccp_class = uconp->sccp_class; + /* generate + send CR PDU to SCRC */ + xua_gen_encode_and_send(conn, event, prim, SUA_CO_CORE); + /* start connection timer */ + conn_start_connect_timer(conn); + osmo_fsm_inst_state_chg(fi, S_CONN_PEND_OUT, 0, 0); + break; +#if 0 + case SCOC_E_SCU_N_TYPE1_REQ: + /* ?!? */ + break; +#endif + case SCOC_E_RCOC_RLSD_IND: + /* send release complete to SCRC */ + xua_gen_encode_and_send(conn, event, NULL, SUA_CO_RELCO); + break; + case SCOC_E_RCOC_REL_COMPL_IND: + /* do nothing */ + break; + case SCOC_E_RCOC_OTHER_NPDU: +#if 0 + if (src_ref) { + /* FIXME: send ERROR to SCRC */ + } +#endif + break; + /* destination node / incoming connection */ + /* Figure C.3 / Q.714 (sheet 1 of 6) */ + case SCOC_E_RCOC_CONN_IND: + xua = data; + /* copy relevant parameters from xua to conn */ + sua_addr_parse(&conn->calling_addr, xua, SUA_IEI_SRC_ADDR); + sua_addr_parse(&conn->called_addr, xua, SUA_IEI_DEST_ADDR); + conn->remote_ref = xua_msg_get_u32(xua, SUA_IEI_SRC_REF); + conn->sccp_class = xua_msg_get_u32(xua, SUA_IEI_PROTO_CLASS) & 3; + conn->importance = xua_msg_get_u32(xua, SUA_IEI_IMPORTANCE); + /* 3.1.6.1 The originating node of the CR message + * (identified by the OPC in the calling party address + * or by default by the OPC in the MTP label, [and the + * MTP-SAP instance]) is associated with the incoming + * connection section. */ + if (conn->calling_addr.presence & OSMO_SCCP_ADDR_T_PC) + conn->remote_pc = conn->calling_addr.pc; + else { + /* Hack to get the MTP label here ?!? */ + conn->remote_pc = xua->mtp.opc; + } + + osmo_fsm_inst_state_chg(fi, S_CONN_PEND_IN, 0, 0); + /* N-CONNECT.ind to User */ + scu_gen_encode_and_send(conn, event, xua, OSMO_SCU_PRIM_N_CONNECT, + PRIM_OP_INDICATION); + break; + } +} + +static void scoc_fsm_idle_onenter(struct osmo_fsm_inst *fi, uint32_t old_state) +{ + conn_destroy(fi->priv); +} + +/* Figure C.3 / Q.714 (sheet 2 of 6) */ +static void scoc_fsm_conn_pend_in(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct sccp_connection *conn = fi->priv; + struct osmo_scu_prim *prim = NULL; + + switch (event) { + case SCOC_E_SCU_N_CONN_RESP: + prim = data; + /* FIXME: assign local reference (only now?) */ + /* FIXME: assign sls, protocol class and credit */ + xua_gen_encode_and_send(conn, event, prim, SUA_CO_COAK); + /* start inactivity timers */ + conn_start_inact_timers(conn); + osmo_fsm_inst_state_chg(fi, S_ACTIVE, 0, 0); + break; + case SCOC_E_SCU_N_DISC_REQ: + prim = data; + /* release resources: implicit */ + xua_gen_encode_and_send(conn, event, prim, SUA_CO_COREF); + osmo_fsm_inst_state_chg(fi, S_IDLE, 0, 0); + break; + } +} + +/* Figure C.2/Q.714 (sheet 2 of 7) */ +static void scoc_fsm_conn_pend_out(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct sccp_connection *conn = fi->priv; + struct osmo_scu_prim *prim = NULL; + struct xua_msg *xua = NULL; + + switch (event) { + case SCOC_E_SCU_N_DISC_REQ: + prim = data; + conn->release_cause = prim->u.disconnect.cause; + osmo_fsm_inst_state_chg(fi, S_WAIT_CONN_CONF, 0, 0); + /* keep conn timer running(!) */ + break; + case SCOC_E_CONN_TMR_EXP: + /* N-DISCONNECT.ind to user */ + scu_gen_encode_and_send(conn, event, xua, OSMO_SCU_PRIM_N_DISCONNECT, + PRIM_OP_INDICATION); + /* below implicitly releases resources + local ref */ + osmo_fsm_inst_state_chg(fi, S_IDLE, 0, 0); + break; + case SCOC_E_RCOC_ROUT_FAIL_IND: + case SCOC_E_RCOC_CREF_IND: + xua = data; + /* stop conn timer */ + conn_stop_connect_timer(conn); + /* release local res + ref (implicit by going to idle) */ + /* N-DISCONNECT.ind to user */ + scu_gen_encode_and_send(conn, event, xua, OSMO_SCU_PRIM_N_DISCONNECT, + PRIM_OP_INDICATION); + /* below implicitly releases resources + local ref */ + osmo_fsm_inst_state_chg(fi, S_IDLE, 0, 0); + break; + case SCOC_E_RCOC_RLSD_IND: + xua = data; + /* RLC to SCRC */ + xua_gen_encode_and_send(conn, event, NULL, SUA_CO_RELCO); + /* stop conn timer */ + conn_stop_connect_timer(conn); + /* release local res + ref (implicit) */ + /* N-DISCONNECT.ind to user */ + scu_gen_encode_and_send(conn, event, xua, OSMO_SCU_PRIM_N_DISCONNECT, + PRIM_OP_INDICATION); + osmo_fsm_inst_state_chg(fi, S_IDLE, 0, 0); + break; + case SCOC_E_RCOC_OTHER_NPDU: + xua = data; + conn_start_connect_timer(conn); + /* release local res + ref (implicit) */ + /* N-DISCONNECT.ind to user */ + scu_gen_encode_and_send(conn, event, xua, OSMO_SCU_PRIM_N_DISCONNECT, + PRIM_OP_INDICATION); + osmo_fsm_inst_state_chg(fi, S_IDLE, 0, 0); + break; + case SCOC_E_RCOC_CC_IND: + xua = data; + /* stop conn timer */ + conn_stop_connect_timer(conn); + /* start inactivity timers */ + conn_start_inact_timers(conn); + /* TODO: assign PCU and credit */ + /* associate remote ref to conn */ + conn->remote_ref = xua_msg_get_u32(xua, SUA_IEI_SRC_REF); + /* 3.1.4.2 The node sending the CC message (identified + * by the parameter OPC contained in the + * MTP-TRANSFER.indication primitive which conveyed the + * CC message [plus the MTP-SAP instance]) is associated + * with the connection section. */ + conn->remote_pc = xua->mtp.opc; + + osmo_fsm_inst_state_chg(fi, S_ACTIVE, 0, 0); + /* N-CONNECT.conf to user */ + scu_gen_encode_and_send(conn, event, xua, OSMO_SCU_PRIM_N_CONNECT, + PRIM_OP_CONFIRM); + break; + } +} + +/* Figure C.2/Q.714 (sheet 3 of 7) */ +static void scoc_fsm_wait_conn_conf(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct sccp_connection *conn = fi->priv; + struct xua_msg *xua = NULL; + + switch (event) { + case SCOC_E_RCOC_RLSD_IND: + xua = data; + /* release complete to SCRC */ + xua_gen_encode_and_send(conn, event, NULL, SUA_CO_RELCO); + /* stop conn timer */ + conn_stop_connect_timer(conn); + /* release local res + ref (implicit) */ + osmo_fsm_inst_state_chg(fi, S_IDLE, 0, 0); + break; + case SCOC_E_RCOC_CC_IND: + xua = data; + /* stop conn timer */ + conn_stop_connect_timer(conn); + /* associate rem ref to conn */ + conn->remote_ref = xua_msg_get_u32(xua, SUA_IEI_SRC_REF); + /* released to SCRC */ + xua_gen_relre_and_send(conn, conn->release_cause, NULL); + /* start rel timer */ + conn_start_rel_timer(conn); + osmo_fsm_inst_state_chg(fi, S_DISCONN_PEND, 0, 0); + break; + case SCOC_E_RCOC_OTHER_NPDU: + case SCOC_E_RCOC_CREF_IND: + case SCOC_E_RCOC_ROUT_FAIL_IND: + xua = data; + /* stop conn timer */ + conn_stop_connect_timer(conn); + /* release local res + ref */ + osmo_fsm_inst_state_chg(fi, S_IDLE, 0, 0); + break; + case SCOC_E_CONN_TMR_EXP: + /* release local res + ref */ + osmo_fsm_inst_state_chg(fi, S_IDLE, 0, 0); + break; + } +} + +/* C.2/Q.714 (sheet 4+5 of 7) and C.3/Q714 (sheet 3+4 of 6) */ +static void scoc_fsm_active(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct xua_msg *xua = data; + struct sccp_connection *conn = fi->priv; + struct osmo_scu_prim *prim = NULL; + + switch (event) { + /* TODO: internal disco */ + /* send N-DISCONNECT.ind to user */ + scu_gen_encode_and_send(conn, event, xua, OSMO_SCU_PRIM_N_DISCONNECT, + PRIM_OP_INDICATION); + /* fall-through */ + case SCOC_E_SCU_N_DISC_REQ: + prim = data; + /* stop inact timers */ + conn_stop_inact_timers(conn); + /* send RLSD to SCRC */ + xua_gen_encode_and_send(conn, event, prim, SUA_CO_RELRE); + /* start rel timer */ + conn_start_rel_timer(conn); + osmo_fsm_inst_state_chg(fi, S_DISCONN_PEND, 0, 0); + break; + case SCOC_E_RCOC_CREF_IND: + case SCOC_E_RCOC_CC_IND: + case SCOC_E_RCOC_REL_COMPL_IND: + /* do nothing */ + break; + case SCOC_E_RCOC_RLSD_IND: + /* send N-DISCONNECT.ind to user */ + scu_gen_encode_and_send(conn, event, xua, OSMO_SCU_PRIM_N_DISCONNECT, + PRIM_OP_INDICATION); + /* release res + local ref (implicit) */ + /* stop inact timers */ + conn_stop_inact_timers(conn); + /* RLC to SCRC */ + xua_gen_encode_and_send(conn, event, NULL, SUA_CO_RELCO); + osmo_fsm_inst_state_chg(fi, S_IDLE, 0, 0); + break; + case SCOC_E_RCOC_ERROR_IND: + xua = data; + /* FIXME: check for cause service_class_mismatch */ + /* release res + local ref (implicit) */ + /* send N-DISCONNECT.ind to user */ + scu_gen_encode_and_send(conn, event, xua, OSMO_SCU_PRIM_N_DISCONNECT, + PRIM_OP_INDICATION); + /* stop inact timers */ + conn_stop_inact_timers(conn); + osmo_fsm_inst_state_chg(fi, S_IDLE, 0, 0); + break; + case SCOC_E_T_IAR_EXP: + /* Send N-DISCONNECT.ind to local user */ + scu_gen_encode_and_send(conn, event, NULL, OSMO_SCU_PRIM_N_DISCONNECT, + PRIM_OP_INDICATION); + /* Send RLSD to peer */ + xua_gen_relre_and_send(conn, SCCP_RELEASE_CAUSE_EXPIRATION_INACTIVE, NULL); + conn_start_rel_timer(conn); + osmo_fsm_inst_state_chg(fi, S_DISCONN_PEND, 0, 0); + break; + case SCOC_E_RCOC_ROUT_FAIL_IND: + /* send N-DISCONNECT.ind to user */ + scu_gen_encode_and_send(conn, event, NULL, OSMO_SCU_PRIM_N_DISCONNECT, + PRIM_OP_INDICATION); + /* stop inact timers */ + conn_stop_inact_timers(conn); + /* start release timer */ + conn_start_rel_timer(conn); + osmo_fsm_inst_state_chg(fi, S_DISCONN_PEND, 0, 0); + break; + /* Figure C.4/Q.714 */ + case SCOC_E_SCU_N_DATA_REQ: + case SCOC_E_SCU_N_EXP_DATA_REQ: + prim = data; + xua_gen_encode_and_send(conn, event, prim, SUA_CO_CODT); + conn_restart_tx_inact_timer(conn); + break; + case SCOC_E_RCOC_DT1_IND: + /* restart receive inactivity timer */ + conn_restart_rx_inact_timer(conn); + /* TODO: M-bit */ + scu_gen_encode_and_send(conn, event, xua, OSMO_SCU_PRIM_N_DATA, + PRIM_OP_INDICATION); + break; + /* Figure C.4/Q.714 (sheet 4 of 4) */ + case SCOC_E_RCOC_IT_IND: + xua = data; + /* check if remote reference is what we expect */ + /* check class is what we expect */ + if (xua_msg_get_u32(xua, SUA_IEI_SRC_REF) != conn->remote_ref || + xua_msg_get_u32(xua, SUA_IEI_PROTO_CLASS) != conn->sccp_class) { + /* Release connection */ + /* send N-DISCONNECT.ind to user */ + scu_gen_encode_and_send(conn, event, NULL, + OSMO_SCU_PRIM_N_DISCONNECT, + PRIM_OP_INDICATION); + /* Stop inactivity Timers */ + conn_stop_inact_timers(conn); + /* Send RLSD to SCRC */ + xua_gen_relre_and_send(conn, SCCP_RELEASE_CAUSE_INCONSISTENT_CONN_DATA, NULL); + /* Start release timer */ + conn_start_rel_timer(conn); + osmo_fsm_inst_state_chg(fi, S_DISCONN_PEND, 0, 0); + } + conn_restart_rx_inact_timer(conn); + break; + case SCOC_E_T_IAS_EXP: + /* Send IT to peer */ + xua_gen_encode_and_send(conn, event, NULL, SUA_CO_COIT); + conn_restart_tx_inact_timer(conn); + break; + } +} + +/* C.2/Q.714 (sheet 6+7 of 7) and C.3/Q.714 (sheet 5+6 of 6) */ +static void scoc_fsm_disconn_pend(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct sccp_connection *conn = fi->priv; + + switch (event) { + case SCOC_E_RCOC_REL_COMPL_IND: + case SCOC_E_RCOC_RLSD_IND: + /* release res + local ref (implicit) */ + /* freeze local ref */ + /* stop release + interval timers */ + conn_stop_release_timers(conn); + osmo_fsm_inst_state_chg(fi, S_IDLE, 0, 0); + break; + case SCOC_E_RCOC_ROUT_FAIL_IND: + case SCOC_E_RCOC_OTHER_NPDU: + /* do nothing */ + break; + case SCOC_E_T_REL_EXP: /* release timer exp */ + /* send RLSD */ + xua_gen_relre_and_send(conn, SCCP_RELEASE_CAUSE_UNQUALIFIED, NULL); + /* start interval timer */ + conn_start_int_timer(conn); + /* start repeat release timer */ + conn_start_rep_rel_timer(conn); + break; + case SCOC_E_T_INT_EXP: /* interval timer exp */ + /* TODO: Inform maintenance */ + /* stop release and interval timers */ + conn_stop_release_timers(conn); + osmo_fsm_inst_state_chg(fi, S_IDLE, 0, 0); + break; + case SCOC_E_T_REP_REL_EXP: /* repeat release timer exp */ + /* send RLSD */ + xua_gen_relre_and_send(conn, SCCP_RELEASE_CAUSE_UNQUALIFIED, NULL); + /* re-start repeat release timer */ + conn_start_rep_rel_timer(conn); + break; + } +} + +static const struct osmo_fsm_state sccp_scoc_states[] = { + [S_IDLE] = { + .name = "IDLE", + .action = scoc_fsm_idle, + .onenter= scoc_fsm_idle_onenter, + .in_event_mask = S(SCOC_E_SCU_N_CONN_REQ) | + //S(SCOC_E_SCU_N_TYPE1_REQ) | + S(SCOC_E_RCOC_CONN_IND) | + S(SCOC_E_RCOC_RLSD_IND) | + S(SCOC_E_RCOC_REL_COMPL_IND) | + S(SCOC_E_RCOC_OTHER_NPDU), + .out_state_mask = S(S_CONN_PEND_OUT) | + S(S_CONN_PEND_IN), + }, + [S_CONN_PEND_IN] = { + .name = "CONN_PEND_IN", + .action = scoc_fsm_conn_pend_in, + .in_event_mask = S(SCOC_E_SCU_N_CONN_RESP) | + S(SCOC_E_SCU_N_DISC_REQ), + .out_state_mask = S(S_IDLE) | + S(S_ACTIVE), + }, + [S_CONN_PEND_OUT] = { + .name = "CONN_PEND_OUT", + .action = scoc_fsm_conn_pend_out, + .in_event_mask = S(SCOC_E_SCU_N_DISC_REQ) | + S(SCOC_E_CONN_TMR_EXP) | + S(SCOC_E_RCOC_ROUT_FAIL_IND) | + S(SCOC_E_RCOC_RLSD_IND) | + S(SCOC_E_RCOC_OTHER_NPDU) | + S(SCOC_E_RCOC_CREF_IND) | + S(SCOC_E_RCOC_CC_IND), + .out_state_mask = S(S_IDLE) | + S(S_ACTIVE) | + S(S_WAIT_CONN_CONF), + }, + [S_ACTIVE] = { + .name = "ACTIVE", + .action = scoc_fsm_active, + .in_event_mask = S(SCOC_E_SCU_N_DISC_REQ) | + /* internal disconnect */ + S(SCOC_E_RCOC_CREF_IND) | + S(SCOC_E_RCOC_REL_COMPL_IND) | + S(SCOC_E_RCOC_RLSD_IND) | + S(SCOC_E_RCOC_ERROR_IND) | + S(SCOC_E_T_IAR_EXP) | + S(SCOC_E_T_IAS_EXP) | + S(SCOC_E_RCOC_ROUT_FAIL_IND) | + S(SCOC_E_SCU_N_DATA_REQ) | + S(SCOC_E_SCU_N_EXP_DATA_REQ) | + S(SCOC_E_RCOC_DT1_IND) | + S(SCOC_E_RCOC_IT_IND), + .out_state_mask = S(S_IDLE) | + S(S_DISCONN_PEND), + }, + [S_DISCONN_PEND] = { + .name = "DISCONN_PEND", + .action = scoc_fsm_disconn_pend, + .in_event_mask = S(SCOC_E_RCOC_REL_COMPL_IND) | + S(SCOC_E_RCOC_RLSD_IND) | + S(SCOC_E_RCOC_ROUT_FAIL_IND) | + S(SCOC_E_RCOC_OTHER_NPDU) | + S(SCOC_E_T_REL_EXP) | + S(SCOC_E_T_INT_EXP) | + S(SCOC_E_T_REP_REL_EXP), + .out_state_mask = S(S_IDLE), + }, + [S_RESET_IN] = { + .name = "RESET_IN", + }, + [S_RESET_OUT] = { + .name = "RESET_OUT", + }, + [S_BOTHWAY_RESET] = { + .name = "BOTHWAY_RESET", + }, + [S_WAIT_CONN_CONF] = { + .name = "WAIT_CONN_CONF", + .action = scoc_fsm_wait_conn_conf, + .in_event_mask = S(SCOC_E_RCOC_RLSD_IND) | + S(SCOC_E_RCOC_CC_IND) | + S(SCOC_E_RCOC_OTHER_NPDU) | + S(SCOC_E_CONN_TMR_EXP) | + S(SCOC_E_RCOC_CREF_IND) | + S(SCOC_E_RCOC_ROUT_FAIL_IND), + }, +}; + +struct osmo_fsm sccp_scoc_fsm = { + .name = "SCCP-SCOC", + .states = sccp_scoc_states, + .num_states = ARRAY_SIZE(sccp_scoc_states), + /* ".log_subsys = DLSCCP" doesn't work as DLSCCP is not a constant */ + .event_names = scoc_event_names, +}; + +/* map from SCCP return cause to SCCP Refusal cause */ +static const uint8_t cause_map_cref[] = { + [SCCP_RETURN_CAUSE_SUBSYSTEM_CONGESTION] = + SCCP_REFUSAL_SUBSYTEM_CONGESTION, + [SCCP_RETURN_CAUSE_SUBSYSTEM_FAILURE] = + SCCP_REFUSAL_SUBSYSTEM_FAILURE, + [SCCP_RETURN_CAUSE_UNEQUIPPED_USER] = + SCCP_REFUSAL_UNEQUIPPED_USER, + [SCCP_RETURN_CAUSE_UNQUALIFIED] = + SCCP_REFUSAL_UNQUALIFIED, + [SCCP_RETURN_CAUSE_SCCP_FAILURE] = + SCCP_REFUSAL_SCCP_FAILURE, + [SCCP_RETURN_CAUSE_HOP_COUNTER_VIOLATION] = + SCCP_REFUSAL_HOP_COUNTER_VIOLATION, +}; + +static uint8_t get_cref_cause_for_ret(uint8_t ret_cause) +{ + if (ret_cause < ARRAY_SIZE(cause_map_cref)) + return cause_map_cref[ret_cause]; + else + return SCCP_REFUSAL_UNQUALIFIED; +} + +/* Generate a COREF message purely based on an incoming SUA message, + * without the use of any local connection state */ +static struct xua_msg *gen_coref_without_conn(struct osmo_sccp_instance *inst, + struct xua_msg *xua_in, + uint32_t ref_cause) +{ + struct xua_msg *xua; + + xua = xua_msg_alloc(); + xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_COREF); + xua_msg_add_u32(xua, SUA_IEI_ROUTE_CTX, inst->route_ctx); + + xua_msg_copy_part(xua, SUA_IEI_DEST_REF, xua_in, SUA_IEI_SRC_REF); + xua_msg_add_u32(xua, SUA_IEI_CAUSE, SUA_CAUSE_T_REFUSAL | ref_cause); + /* optional: source addr */ + xua_msg_copy_part(xua, SUA_IEI_SRC_ADDR, xua_in, SUA_IEI_DEST_ADDR); + /* conditional: dest addr */ + xua_msg_copy_part(xua, SUA_IEI_DEST_ADDR, xua_in, SUA_IEI_SRC_ADDR); + /* optional: importance */ + xua_msg_copy_part(xua, SUA_IEI_IMPORTANCE, xua_in, SUA_IEI_IMPORTANCE); + /* optional: data */ + xua_msg_copy_part(xua, SUA_IEI_DATA, xua_in, SUA_IEI_DATA); + + return xua; +} + +/*! \brief SCOC: Receive SCRC Routing Failure + * \param[in] inst SCCP Instance on which we operate + * \param[in] xua SUA message that was failed to route + * \param[in] return_cause Reason (cause) for routing failure */ +void sccp_scoc_rx_scrc_rout_fail(struct osmo_sccp_instance *inst, + struct xua_msg *xua, uint32_t return_cause) +{ + uint32_t conn_id; + struct sccp_connection *conn; + + /* try to dispatch to connection FSM (if any) */ + conn_id = xua_msg_get_u32(xua, SUA_IEI_DEST_REF); + conn = conn_find_by_id(inst, conn_id); + if (conn) { + osmo_fsm_inst_dispatch(conn->fi, + SCOC_E_RCOC_ROUT_FAIL_IND, xua); + } else { + /* generate + send CREF directly */ + struct xua_msg *cref; + uint8_t cref_cause = get_cref_cause_for_ret(return_cause); + cref = gen_coref_without_conn(inst, xua, cref_cause); + sccp_scrc_rx_scoc_conn_msg(inst, cref); + xua_msg_free(cref); + } +} + +/* Find a SCCP user for given SUA message (based on SUA_IEI_DEST_ADDR */ +static struct osmo_sccp_user *sccp_find_user(struct osmo_sccp_instance *inst, + struct xua_msg *xua) +{ + int rc; + struct osmo_sccp_addr called_addr; + + rc = sua_addr_parse(&called_addr, xua, SUA_IEI_DEST_ADDR); + if (rc < 0) { + LOGP(DLSCCP, LOGL_ERROR, "Cannot find SCCP User for XUA " + "Message %s without valid DEST_ADDR\n", + xua_hdr_dump(xua, &xua_dialect_sua)); + return NULL; + } + + if (!(called_addr.presence & OSMO_SCCP_ADDR_T_SSN)) { + LOGP(DLSCCP, LOGL_ERROR, "Cannot resolve SCCP User for " + "XUA Message %s without SSN in CalledAddr\n", + xua_hdr_dump(xua, &xua_dialect_sua)); + return NULL; + } + + return sccp_user_find(inst, called_addr.ssn, called_addr.pc); +} + +/* Generate a COERR based in input arguments */ +static struct xua_msg *gen_coerr(uint32_t route_ctx, uint32_t dest_ref, + uint32_t err_cause) +{ + struct xua_msg *xua = xua_msg_alloc(); + + xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_COERR); + xua_msg_add_u32(xua, SUA_IEI_ROUTE_CTX, route_ctx); + xua_msg_add_u32(xua, SUA_IEI_DEST_REF, dest_ref); + xua_msg_add_u32(xua, SUA_IEI_CAUSE, SUA_CAUSE_T_ERROR | err_cause); + + return xua; +} + +/* generate COERR from incoming XUA and send it */ +static void tx_coerr_from_xua(struct osmo_sccp_instance *inst, + struct xua_msg *in, uint32_t err_cause) +{ + struct xua_msg *xua; + uint32_t route_ctx, dest_ref; + + route_ctx = xua_msg_get_u32(in, SUA_IEI_ROUTE_CTX); + /* get *source* reference and use as destination ref */ + dest_ref = xua_msg_get_u32(in, SUA_IEI_SRC_REF); + + xua = gen_coerr(route_ctx, dest_ref, err_cause); + /* copy over the MTP parameters */ + xua->mtp.dpc = in->mtp.opc; + xua->mtp.opc = in->mtp.dpc; + xua->mtp.sio = in->mtp.sio; + + /* sent to SCRC for transmission */ + sccp_scrc_rx_scoc_conn_msg(inst, xua); + xua_msg_free(xua); +} + +/* Generate a RELCO based in input arguments */ +static struct xua_msg *gen_relco(uint32_t route_ctx, uint32_t dest_ref, + uint32_t src_ref) +{ + struct xua_msg *xua = xua_msg_alloc(); + + xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_RELCO); + xua_msg_add_u32(xua, SUA_IEI_ROUTE_CTX, route_ctx); + xua_msg_add_u32(xua, SUA_IEI_DEST_REF, dest_ref); + xua_msg_add_u32(xua, SUA_IEI_SRC_REF, src_ref); + + return xua; +} + +/* generate RELCO from incoming XUA and send it */ +static void tx_relco_from_xua(struct osmo_sccp_instance *inst, + struct xua_msg *in) +{ + struct xua_msg *xua; + uint32_t route_ctx, dest_ref, src_ref; + + route_ctx = xua_msg_get_u32(in, SUA_IEI_ROUTE_CTX); + /* get *source* reference and use as destination ref */ + dest_ref = xua_msg_get_u32(in, SUA_IEI_SRC_REF); + /* get *dest* reference and use as source ref */ + src_ref = xua_msg_get_u32(in, SUA_IEI_DEST_REF); + + xua = gen_relco(route_ctx, dest_ref, src_ref); + /* copy over the MTP parameters */ + xua->mtp.dpc = in->mtp.opc; + xua->mtp.opc = in->mtp.dpc; + xua->mtp.sio = in->mtp.sio; + + /* send to SCRC for transmission */ + sccp_scrc_rx_scoc_conn_msg(inst, xua); + xua_msg_free(xua); +} + +/* Generate a RLSD based in input arguments */ +static struct xua_msg *gen_rlsd(uint32_t route_ctx, uint32_t dest_ref, + uint32_t src_ref) +{ + struct xua_msg *xua = xua_msg_alloc(); + + xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_RELRE); + xua_msg_add_u32(xua, SUA_IEI_ROUTE_CTX, route_ctx); + xua_msg_add_u32(xua, SUA_IEI_DEST_REF, dest_ref); + xua_msg_add_u32(xua, SUA_IEI_SRC_REF, src_ref); + + return xua; +} + +/* Generate a RLSD to both the remote side and the local conn */ +static void tx_rlsd_from_xua_twoway(struct sccp_connection *conn, + struct xua_msg *in) +{ + struct xua_msg *xua; + uint32_t route_ctx, dest_ref, src_ref; + + route_ctx = xua_msg_get_u32(in, SUA_IEI_ROUTE_CTX); + /* get *source* reference and use as destination ref */ + dest_ref = xua_msg_get_u32(in, SUA_IEI_SRC_REF); + /* get *source* reference and use as destination ref */ + src_ref = xua_msg_get_u32(in, SUA_IEI_DEST_REF); + + /* Generate RLSD towards remote peer */ + xua = gen_rlsd(route_ctx, dest_ref, src_ref); + /* copy over the MTP parameters */ + xua->mtp.dpc = in->mtp.opc; + xua->mtp.opc = in->mtp.dpc; + xua->mtp.sio = in->mtp.sio; + /* send to SCRC for transmission */ + sccp_scrc_rx_scoc_conn_msg(conn->inst, xua); + xua_msg_free(xua); + + /* Generate RLSD towards local peer */ + xua = gen_rlsd(conn->inst->route_ctx, conn->conn_id, conn->remote_ref); + xua->mtp.dpc = in->mtp.dpc; + xua->mtp.opc = conn->remote_pc; + xua->mtp.sio = in->mtp.sio; + osmo_fsm_inst_dispatch(conn->fi, SCOC_E_RCOC_RLSD_IND, xua); + xua_msg_free(xua); +} + +/* process received message for unasigned local reference */ +static void sccp_scoc_rx_unass_local_ref(struct osmo_sccp_instance *inst, + struct xua_msg *xua) +{ + /* we have received a message with unassigned destination local + * reference and thus apply the action indicated in Table + * B.2/Q.714 */ + switch (xua->hdr.msg_type) { + case SUA_CO_COAK: /* CC */ + case SUA_CO_COIT: /* IT */ + case SUA_CO_RESRE: /* RSR */ + case SUA_CO_RESCO: /* RSC */ + /* Send COERR */ + tx_coerr_from_xua(inst, xua, SCCP_ERROR_LRN_MISMATCH_UNASSIGNED); + break; + case SUA_CO_COREF: /* CREF */ + case SUA_CO_RELCO: /* RLC */ + case SUA_CO_CODT: /* DT1 */ + case SUA_CO_CODA: /* AK */ + case SUA_CO_COERR: /* ERR */ + /* DISCARD */ + break; + case SUA_CO_RELRE: /* RLSD */ + /* Send RLC */ + tx_relco_from_xua(inst, xua); + break; + default: + LOGP(DLSCCP, LOGL_NOTICE, "Unhandled %s\n", + xua_hdr_dump(xua, &xua_dialect_sua)); + break; + } +} + +/* process received message for invalid source local reference */ +static void sccp_scoc_rx_inval_src_ref(struct sccp_connection *conn, + struct xua_msg *xua) +{ + /* we have received a message with invalid source local + * reference and thus apply the action indicated in Table + * B.2/Q.714 */ + switch (xua->hdr.msg_type) { + case SUA_CO_RELRE: /* RLSD */ + case SUA_CO_RESRE: /* RSR */ + case SUA_CO_RESCO: /* RSC */ + /* Send ERR */ + tx_coerr_from_xua(conn->inst, xua, SCCP_ERROR_LRN_MISMATCH_INCONSISTENT); + break; + case SUA_CO_COIT: /* IT */ + /* FIXME: RLSD to both sides */ + tx_rlsd_from_xua_twoway(conn, xua); + break; + case SUA_CO_RELCO: /* RLC */ + /* DISCARD */ + break; + default: + LOGP(DLSCCP, LOGL_NOTICE, "Unhandled %s\n", + xua_hdr_dump(xua, &xua_dialect_sua)); + break; + } +} + +/* process received message for invalid origin point code */ +static void sccp_scoc_rx_inval_opc(struct osmo_sccp_instance *inst, + struct xua_msg *xua) +{ + /* we have received a message with invalid origin PC and thus + * apply the action indiacted in Table B.2/Q.714 */ + switch (xua->hdr.msg_type) { + case SUA_CO_RELRE: /* RLSD */ + case SUA_CO_RESRE: /* RSR */ + case SUA_CO_RESCO: /* RSC */ + /* Send ERR */ + tx_coerr_from_xua(inst, xua, SCCP_ERROR_POINT_CODE_MISMATCH); + break; + case SUA_CO_RELCO: /* RLC */ + case SUA_CO_CODT: /* DT1 */ + case SUA_CO_CODA: /* AK */ + case SUA_CO_COERR: /* ERR */ + /* DISCARD */ + break; + default: + LOGP(DLSCCP, LOGL_NOTICE, "Unhandled %s\n", + xua_hdr_dump(xua, &xua_dialect_sua)); + break; + } +} + +/*! \brief Main entrance function for primitives from the SCRC (Routing Control) + * \param[in] inst SCCP Instance in which we operate + * \param[in] xua SUA message in xua_msg format */ +void sccp_scoc_rx_from_scrc(struct osmo_sccp_instance *inst, + struct xua_msg *xua) +{ + struct sccp_connection *conn; + struct osmo_sccp_user *scu; + uint32_t src_loc_ref; + int event; + + /* we basically try to convert the SUA message into an event, + * and then dispatch the event to the connection-specific FSM. + * If it is a CORE (Connect REquest), we create the connection + * (and imlpicitly its FSM) first */ + + if (xua->hdr.msg_type == SUA_CO_CORE) { + scu = sccp_find_user(inst, xua); + if (!scu) { + /* this shouldn't happen, as the caller should + * have already verified that a local user is + * equipped for this SSN */ + LOGP(DLSCCP, LOGL_ERROR, "Cannot find user for " + "CORE ?!?\n"); + return; + } + /* Allocate new connection */ + conn = conn_create(inst); + conn->user = scu; + } else { + uint32_t conn_id; + /* Resolve existing connection */ + conn_id = xua_msg_get_u32(xua, SUA_IEI_DEST_REF); + conn = conn_find_by_id(inst, conn_id); + if (!conn) { + LOGP(DLSCCP, LOGL_NOTICE, "Cannot find connection for " + "local reference %u\n", conn_id); + sccp_scoc_rx_unass_local_ref(inst, xua); + return; + } + } + OSMO_ASSERT(conn); + OSMO_ASSERT(conn->fi); + + DEBUGP(DLSCCP, "Received %s for local reference %u\n", + xua_hdr_dump(xua, &xua_dialect_sua), conn->conn_id); + + if (xua->hdr.msg_type != SUA_CO_CORE && + xua->hdr.msg_type != SUA_CO_COAK && + xua->hdr.msg_type != SUA_CO_COREF) { + if (xua_msg_find_tag(xua, SUA_IEI_SRC_REF)) { + /* Check if received source local reference != + * the one we saved in local state */ + src_loc_ref = xua_msg_get_u32(xua, SUA_IEI_SRC_REF); + if (src_loc_ref != conn->remote_ref) { + sccp_scoc_rx_inval_src_ref(conn, xua); + return; + } + } + + /* Check if received OPC != the remote_pc we stored locally */ + if (xua->mtp.opc != conn->remote_pc) { + sccp_scoc_rx_inval_opc(inst, xua); + return; + } + } + + /* Map from XUA message to event */ + event = xua_msg_event_map(xua, sua_scoc_event_map, ARRAY_SIZE(sua_scoc_event_map)); + if (event < 0) { + LOGP(DLSCCP, LOGL_ERROR, "Cannot map SCRC msg %s to event\n", + xua_hdr_dump(xua, &xua_dialect_sua)); + /* Table B.1/Q714 states DISCARD for any message with + * unknown type */ + return; + } + + /* Dispatch event to existing connection */ + osmo_fsm_inst_dispatch(conn->fi, event, xua); +} + +/* get the Connection ID of the given SCU primitive */ +static uint32_t scu_prim_conn_id(const struct osmo_scu_prim *prim) +{ + switch (prim->oph.primitive) { + case OSMO_SCU_PRIM_N_CONNECT: + return prim->u.connect.conn_id; + case OSMO_SCU_PRIM_N_DATA: + return prim->u.data.conn_id; + case OSMO_SCU_PRIM_N_DISCONNECT: + return prim->u.disconnect.conn_id; + case OSMO_SCU_PRIM_N_RESET: + return prim->u.reset.conn_id; + default: + return 0; + } +} + +/*! \brief Main entrance function for primitives from SCCP User + * \param[in] scu SCCP User sending us the primitive + * \param[in] oph Osmocom primitive sent by the user + * \returns 0 on success; negative on error */ +int osmo_sccp_user_sap_down(struct osmo_sccp_user *scu, struct osmo_prim_hdr *oph) +{ + struct osmo_scu_prim *prim = (struct osmo_scu_prim *) oph; + struct osmo_sccp_instance *inst = scu->inst; + struct msgb *msg = prim->oph.msg; + struct sccp_connection *conn; + int rc = 0; + int event; + + LOGP(DLSCCP, LOGL_DEBUG, "Received SCCP User Primitive %s)\n", + osmo_scu_prim_name(&prim->oph)); + + switch (OSMO_PRIM_HDR(&prim->oph)) { + case OSMO_PRIM(OSMO_SCU_PRIM_N_UNITDATA, PRIM_OP_REQUEST): + /* other CL primitives? */ + /* Connectionless by-passes this altogether */ + return sccp_sclc_user_sap_down(scu, oph); + case OSMO_PRIM(OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_REQUEST): + /* Allocate new connection structure */ + conn = conn_create_id(inst, prim->u.connect.conn_id); + if (!conn) { + /* FIXME: inform user */ + goto out; + } + conn->user = scu; + break; + case OSMO_PRIM(OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_RESPONSE): + case OSMO_PRIM(OSMO_SCU_PRIM_N_DATA, PRIM_OP_REQUEST): + case OSMO_PRIM(OSMO_SCU_PRIM_N_DISCONNECT, PRIM_OP_REQUEST): + case OSMO_PRIM(OSMO_SCU_PRIM_N_RESET, PRIM_OP_REQUEST): + /* Resolve existing connection structure */ + conn = conn_find_by_id(inst, scu_prim_conn_id(prim)); + if (!conn) { + /* FIXME: inform user */ + goto out; + } + break; + } + + /* Map from primitive to event */ + event = osmo_event_for_prim(oph, scu_scoc_event_map); + + /* Dispatch event into connection */ + rc = osmo_fsm_inst_dispatch(conn->fi, event, prim); +out: + /* the SAP is supposed to consume the primitive/msgb */ + msgb_free(msg); + + return rc; +} + +void sccp_scoc_flush_connections(struct osmo_sccp_instance *inst) +{ + struct sccp_connection *conn, *conn2; + + llist_for_each_entry_safe(conn, conn2, &inst->connections, list) + conn_destroy(conn); +} diff --git a/src/sccp_scrc.c b/src/sccp_scrc.c new file mode 100644 index 0000000..9bccc0a --- /dev/null +++ b/src/sccp_scrc.c @@ -0,0 +1,473 @@ +/* SCCP Routing Control (SCRC) according to ITU-T 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/>. + * + */ + +#include <stdbool.h> + +#include <osmocom/core/logging.h> +#include <osmocom/core/msgb.h> + +#include <sccp/sccp_types.h> +#include <osmocom/sigtran/osmo_ss7.h> +#include <osmocom/sigtran/sccp_sap.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 primtiive */ + 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 + 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); +} + +/* Gererate 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?!?\n"); + 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_M3UA: + 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: Canges 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 */ + /* Node 7 (Sheet 5) */ + return scrc_node_7(inst, xua, called); + } + + /* 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; + + scu = sccp_user_find(inst, called->ssn, called->pc); + + /* Is subsystem equipped? */ + if (!scu) { + /* Error: unequipped user */ + 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 (translate && + (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); +} + +/* 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 */ + + 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); +} diff --git a/src/sccp_user.c b/src/sccp_user.c new file mode 100644 index 0000000..df02486 --- /dev/null +++ b/src/sccp_user.c @@ -0,0 +1,377 @@ +/* SCCP User related routines */ + +/* (C) 2017 by Harald Welte <laforge@gnumonks.org> + * All Rights Reserved + * + * based on my 2011 Erlang implementation osmo_ss7/src/sua_sccp_conv.erl + * + * References: ITU-T Q.713 and IETF RFC 3868 + * + * 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/linuxlist.h> +#include <osmocom/core/logging.h> + +#include <osmocom/sigtran/osmo_ss7.h> +#include <osmocom/sigtran/sccp_sap.h> +#include <osmocom/sigtran/mtp_sap.h> +#include <osmocom/sigtran/protocol/mtp.h> + +#include "sccp_internal.h" +#include "xua_internal.h" + +/*! \brief Find a SCCP User registered for given PC+SSN or SSN only + * \param[in] inst SCCP Instance in which to search + * \param[in] ssn Sub-System Number to search for + * \param[in] pc Point Code to search for + * \returns Matching SCCP User; NULL if none found */ +struct osmo_sccp_user * +sccp_user_find(struct osmo_sccp_instance *inst, uint16_t ssn, uint32_t pc) +{ + struct osmo_sccp_user *scu; + + /* First try to find match for PC + SSN */ + llist_for_each_entry(scu, &inst->users, list) { + if (scu->pc_valid && scu->pc == pc && scu->ssn == ssn) + return scu; + } + + /* Then try to match on SSN only */ + llist_for_each_entry(scu, &inst->users, list) { + if (!scu->pc_valid && scu->ssn == ssn) + return scu; + } + + return NULL; +} + +/*! \brief Bind a SCCP User to a given Point Code + * \param[in] inst SCCP Instance + * \param[in] name human-readable name + * \param[in] ssn Sub-System Number to bind to + * \param[in] pc Point Code to bind to (if any) + * \param[in] pc_valid Whether or not \ref pc is valid/used + * \returns Callee-allocated SCCP User on success; negative otherwise */ +static struct osmo_sccp_user * +sccp_user_bind_pc(struct osmo_sccp_instance *inst, const char *name, + osmo_prim_cb prim_cb, uint16_t ssn, uint32_t pc, bool pc_valid) +{ + struct osmo_sccp_user *scu; + if (!pc_valid) + pc = 0; + + if (sccp_user_find(inst, ssn, pc)) + return NULL; + + LOGP(DLSCCP, LOGL_INFO, "Binding user '%s' to SSN=%u PC=%u (pc_valid=%u)\n", + name, ssn, pc, pc_valid); + + scu = talloc_zero(inst, struct osmo_sccp_user); + scu->name = talloc_strdup(scu, name); + scu->inst = inst; + scu->prim_cb = prim_cb; + scu->ssn = ssn; + scu->pc = pc; + scu->pc_valid = pc_valid; + llist_add_tail(&scu->list, &inst->users); + + return scu; +} + +/*! \brief Bind a given SCCP User to a given SSN+PC + * \param[in] inst SCCP Instance + * \param[in] name human-readable name + * \param[in] ssn Sub-System Number to bind to + * \param[in] pc Point Code to bind to (if any) + * \returns Callee-allocated SCCP User on success; negative otherwise */ +struct osmo_sccp_user * +osmo_sccp_user_bind_pc(struct osmo_sccp_instance *inst, const char *name, + osmo_prim_cb prim_cb, uint16_t ssn, uint32_t pc) +{ + return sccp_user_bind_pc(inst, name, prim_cb, ssn, pc, true); +} + +/*! \brief Bind a given SCCP User to a given SSN (at any PC) + * \param[in] inst SCCP Instance + * \param[in] name human-readable name + * \param[in] ssn Sub-System Number to bind to + * \returns Callee-allocated SCCP User on success; negative otherwise */ +struct osmo_sccp_user * +osmo_sccp_user_bind(struct osmo_sccp_instance *inst, const char *name, + osmo_prim_cb prim_cb, uint16_t ssn) +{ + return sccp_user_bind_pc(inst, name, prim_cb, ssn, 0, false); +} + +/*! \brief Unbind a given SCCP user + * \param[in] scu SCCP User which is to be un-bound. Will be destroyed + * at the time this function returns. */ +void osmo_sccp_user_unbind(struct osmo_sccp_user *scu) +{ + LOGP(DLSCCP, LOGL_INFO, "Unbinding user '%s' from SSN=%u PC=%u " + "(pc_valid=%u)\n", scu->name, scu->ssn, scu->pc, + scu->pc_valid); + /* FIXME: free/release all connections held by this user? */ + llist_del(&scu->list); + talloc_free(scu); +} + +/*! \brief Send a SCCP User SAP Primitive up to the User + * \param[in] scu SCCP User to whom to send the primitive + * \param[in] prim Primitive to send to the user + * \returns return value of the SCCP User's prim_cb() function */ +int sccp_user_prim_up(struct osmo_sccp_user *scu, struct osmo_scu_prim *prim) +{ + LOGP(DLSCCP, LOGL_DEBUG, "Delivering %s to SCCP User '%s'\n", + osmo_scu_prim_name(&prim->oph), scu->name); + return scu->prim_cb(&prim->oph, scu); +} + +/* prim_cb handed to MTP code for incoming MTP-TRANSFER.ind */ +static int mtp_user_prim_cb(struct osmo_prim_hdr *oph, void *ctx) +{ + struct osmo_sccp_instance *inst = ctx; + struct osmo_mtp_prim *omp = (struct osmo_mtp_prim *)oph; + struct xua_msg *xua; + + OSMO_ASSERT(oph->sap == MTP_SAP_USER); + + switch OSMO_PRIM(oph->primitive, oph->operation) { + case OSMO_PRIM(OSMO_MTP_PRIM_TRANSFER, PRIM_OP_INDICATION): + /* Convert from SCCP to SUA in xua_msg format */ + xua = osmo_sccp_to_xua(oph->msg); + xua->mtp = omp->u.transfer; + /* hand this primitive into SCCP via the SCRC code */ + return scrc_rx_mtp_xfer_ind_xua(inst, xua); + default: + LOGP(DLSCCP, LOGL_ERROR, "Unknown primitive %u:%u receivd\n", + oph->primitive, oph->operation); + return -1; + } +} + +static LLIST_HEAD(sccp_instances); + +/*! \brief create a SCCP Instance and register it as user with SS7 inst + * \param[in] ss7 SS7 instance to which this SCCP instance belongs + * \param[in] priv private data to be stored within SCCP instance + * \returns callee-allocated SCCP instance on success; NULL on error */ +struct osmo_sccp_instance * +osmo_sccp_instance_create(struct osmo_ss7_instance *ss7, void *priv) +{ + struct osmo_sccp_instance *inst; + + inst = talloc_zero(ss7, struct osmo_sccp_instance); + if (!inst) + return NULL; + + inst->ss7 = ss7; + inst->priv = priv; + INIT_LLIST_HEAD(&inst->connections); + INIT_LLIST_HEAD(&inst->users); + + inst->ss7_user.inst = ss7; + inst->ss7_user.name = "SCCP"; + inst->ss7_user.prim_cb = mtp_user_prim_cb; + inst->ss7_user.priv = inst; + + osmo_ss7_user_register(ss7, MTP_SI_SCCP, &inst->ss7_user); + + llist_add_tail(&inst->list, &sccp_instances); + + return inst; +} + +void osmo_sccp_instance_destroy(struct osmo_sccp_instance *inst) +{ + struct osmo_sccp_user *scu, *scu2; + + inst->ss7->sccp = NULL; + osmo_ss7_user_unregister(inst->ss7, MTP_SI_SCCP, &inst->ss7_user); + + llist_for_each_entry_safe(scu, scu2, &inst->users, list) { + osmo_sccp_user_unbind(scu); + } + sccp_scoc_flush_connections(inst); + llist_del(&inst->list); + talloc_free(inst); +} + +/*********************************************************************** + * Convenience function for CLIENT + ***********************************************************************/ + +struct osmo_sccp_instance * +osmo_sccp_simple_client(void *ctx, const char *name, uint32_t pc, + enum osmo_ss7_asp_protocol prot, + int local_port, int remote_port, const char *remote_ip) +{ + struct osmo_ss7_instance *ss7; + struct osmo_ss7_as *as; + struct osmo_ss7_route *rt; + struct osmo_ss7_asp *asp; + char *as_name, *asp_name; + + if (!remote_port || remote_port < 0) + remote_port = osmo_ss7_asp_protocol_port(prot); + if (local_port < 0) + local_port = osmo_ss7_asp_protocol_port(prot); + + /* allocate + initialize SS7 instance */ + ss7 = osmo_ss7_instance_find_or_create(ctx, 1); + if (!ss7) + return NULL; + ss7->cfg.primary_pc = pc; + + as_name = talloc_asprintf(ctx, "as-clnt-%s", name); + asp_name = talloc_asprintf(ctx, "asp-clnt-%s", name); + + /* application server */ + as = osmo_ss7_as_find_or_create(ss7, as_name, prot); + if (!as) + goto out_strings; + + /* install default route */ + rt = osmo_ss7_route_create(ss7->rtable_system, 0, 0, as_name); + if (!rt) + goto out_as; + talloc_free(as_name); + + /* application server process */ + asp = osmo_ss7_asp_find_or_create(ss7, asp_name, remote_port, local_port, + prot); + if (!asp) + goto out_rt; + asp->cfg.remote.host = talloc_strdup(asp, remote_ip); + osmo_ss7_as_add_asp(as, asp_name); + talloc_free(asp_name); + osmo_ss7_asp_restart(asp); + + /* Allocate SCCP stack + SCCP user */ + ss7->sccp = osmo_sccp_instance_create(ss7, NULL); + if (!ss7->sccp) + goto out_asp; + + return ss7->sccp; + +out_asp: + osmo_ss7_asp_destroy(asp); +out_rt: + osmo_ss7_route_destroy(rt); +out_as: + osmo_ss7_as_destroy(as); +out_strings: + talloc_free(as_name); + talloc_free(asp_name); + osmo_ss7_instance_destroy(ss7); + + return NULL; +} + +/*********************************************************************** + * Convenience function for SERVER + ***********************************************************************/ + +struct osmo_sccp_instance * +osmo_sccp_simple_server(void *ctx, uint32_t pc, + enum osmo_ss7_asp_protocol prot, int local_port, + const char *local_ip) +{ + struct osmo_ss7_instance *ss7; + struct osmo_xua_server *xs; + + if (local_port < 0) + local_port = osmo_ss7_asp_protocol_port(prot); + + /* allocate + initialize SS7 instance */ + ss7 = osmo_ss7_instance_find_or_create(ctx, 1); + if (!ss7) + return NULL; + ss7->cfg.primary_pc = pc; + + xs = osmo_ss7_xua_server_create(ss7, prot, local_port, local_ip); + if (!xs) + goto out_ss7; + + /* Allocate SCCP stack */ + ss7->sccp = osmo_sccp_instance_create(ss7, NULL); + if (!ss7->sccp) + goto out_xs; + + return ss7->sccp; + +out_xs: + osmo_ss7_xua_server_destroy(xs); +out_ss7: + osmo_ss7_instance_destroy(ss7); + + return NULL; +} + +struct osmo_sccp_instance * +osmo_sccp_simple_server_add_clnt(struct osmo_sccp_instance *inst, + enum osmo_ss7_asp_protocol prot, + const char *name, uint32_t pc, + int local_port, int remote_port, + const char *remote_ip) +{ + struct osmo_ss7_instance *ss7 = inst->ss7; + struct osmo_ss7_as *as; + struct osmo_ss7_route *rt; + struct osmo_ss7_asp *asp; + char *as_name, *asp_name; + + if (local_port < 0) + local_port = osmo_ss7_asp_protocol_port(prot); + + if (remote_port < 0) + remote_port = osmo_ss7_asp_protocol_port(prot); + + as_name = talloc_asprintf(ss7, "as-srv-%s", name); + asp_name = talloc_asprintf(ss7, "asp-srv-%s", name); + + /* application server */ + as = osmo_ss7_as_find_or_create(ss7, as_name, prot); + if (!as) + goto out_strings; + talloc_free(as_name); + + /* route only selected PC to the client */ + rt = osmo_ss7_route_create(ss7->rtable_system, pc, 0xffff, as_name); + if (!rt) + goto out_as; + + asp = osmo_ss7_asp_find_or_create(ss7, asp_name, remote_port, local_port, prot); + if (!asp) + goto out_rt; + asp->cfg.is_server = true; + osmo_ss7_as_add_asp(as, asp_name); + talloc_free(asp_name); + osmo_ss7_asp_restart(asp); + + return ss7->sccp; + +out_rt: + osmo_ss7_route_destroy(rt); +out_as: + osmo_ss7_as_destroy(as); +out_strings: + talloc_free(as_name); + talloc_free(asp_name); + + return NULL; +} |