diff options
Diffstat (limited to 'src/sccp_user.c')
-rw-r--r-- | src/sccp_user.c | 377 |
1 files changed, 377 insertions, 0 deletions
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; +} |