/* Core SS7 AS Handling */ /* (C) 2015-2017 by Harald Welte * (C) 2023 by sysmocom s.f.m.c. GmbH * All Rights Reserved * * SPDX-License-Identifier: GPL-2.0+ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include #include #include #include #include #include #include #include #include #include #include "ss7_as.h" #include "ss7_asp.h" #include "ss7_route.h" #include "ss7_route_table.h" #include "ss7_internal.h" #include "xua_as_fsm.h" #include "xua_asp_fsm.h" /*********************************************************************** * SS7 Application Server ***********************************************************************/ struct value_string osmo_ss7_as_traffic_mode_vals[] = { { OSMO_SS7_AS_TMOD_BCAST, "broadcast" }, { OSMO_SS7_AS_TMOD_LOADSHARE, "loadshare" }, { OSMO_SS7_AS_TMOD_ROUNDROBIN, "round-robin" }, { OSMO_SS7_AS_TMOD_OVERRIDE, "override" }, { 0, NULL } }; static const struct rate_ctr_desc ss7_as_rcd[] = { [SS7_AS_CTR_RX_MSU_TOTAL] = { "rx:msu:total", "Total number of MSU received" }, [SS7_AS_CTR_TX_MSU_TOTAL] = { "tx:msu:total", "Total number of MSU transmitted" }, }; static const struct rate_ctr_group_desc ss7_as_rcgd = { .group_name_prefix = "sigtran_as", .group_description = "SIGTRAN Application Server", .num_ctr = ARRAY_SIZE(ss7_as_rcd), .ctr_desc = ss7_as_rcd, }; static unsigned int g_ss7_as_rcg_idx; /*! \brief Allocate an Application Server * \param[in] inst SS7 Instance on which we operate * \param[in] name Name of Application Server * \param[in] proto Protocol of Application Server * \returns pointer to Application Server on success; NULL otherwise */ struct osmo_ss7_as *ss7_as_alloc(struct osmo_ss7_instance *inst, const char *name, enum osmo_ss7_asp_protocol proto) { struct osmo_ss7_as *as; as = talloc_zero(inst, struct osmo_ss7_as); if (!as) return NULL; as->ctrg = rate_ctr_group_alloc(as, &ss7_as_rcgd, g_ss7_as_rcg_idx++); if (!as->ctrg) { talloc_free(as); return NULL; } rate_ctr_group_set_name(as->ctrg, name); as->inst = inst; as->cfg.name = talloc_strdup(as, name); as->cfg.proto = proto; as->cfg.mode = OSMO_SS7_AS_TMOD_OVERRIDE; as->cfg.recovery_timeout_msec = 2000; as->cfg.routing_key.l_rk_id = ss7_find_free_l_rk_id(inst); as->fi = xua_as_fsm_start(as, LOGL_DEBUG); llist_add_tail(&as->list, &inst->as_list); return as; } /*! \brief Get asp_protocol configuration of a given AS * \param[in] as Application Server in which to look for \ref asp_protocol * \returns The asp_protocol this AS is configured with */ enum osmo_ss7_asp_protocol osmo_ss7_as_get_asp_protocol(const struct osmo_ss7_as *as) { return as->cfg.proto; } /*! \brief Add given ASP to given AS * \param[in] as Application Server to which \ref asp is added * \param[in] asp Application Server Process to be added to \ref as * \returns 0 on success; negative in case of error */ int osmo_ss7_as_add_asp(struct osmo_ss7_as *as, const char *asp_name) { struct osmo_ss7_asp *asp; unsigned int i; OSMO_ASSERT(ss7_initialized); asp = osmo_ss7_asp_find_by_name(as->inst, asp_name); if (!asp) return -ENODEV; LOGPAS(as, DLSS7, LOGL_INFO, "Adding ASP %s to AS\n", asp->cfg.name); if (osmo_ss7_as_has_asp(as, asp)) return 0; for (i = 0; i < ARRAY_SIZE(as->cfg.asps); i++) { if (!as->cfg.asps[i]) { as->cfg.asps[i] = asp; if (asp->fi) osmo_fsm_inst_dispatch(asp->fi, XUA_ASP_E_AS_ASSIGNED, as); return 0; } } return -ENOSPC; } /*! \brief Delete given ASP from given AS * \param[in] as Application Server from which \ref asp is deleted * \param[in] asp Application Server Process to delete from \ref as * \returns 0 on success; negative in case of error */ int osmo_ss7_as_del_asp(struct osmo_ss7_as *as, const char *asp_name) { struct osmo_ss7_asp *asp; unsigned int i; OSMO_ASSERT(ss7_initialized); asp = osmo_ss7_asp_find_by_name(as->inst, asp_name); if (!asp) return -ENODEV; LOGPAS(as, DLSS7, LOGL_INFO, "Removing ASP %s from AS\n", asp->cfg.name); for (i = 0; i < ARRAY_SIZE(as->cfg.asps); i++) { if (as->cfg.asps[i] == asp) { as->cfg.asps[i] = NULL; return 0; } } return -EINVAL; } /*! \brief Destroy given Application Server * \param[in] as Application Server to destroy */ void osmo_ss7_as_destroy(struct osmo_ss7_as *as) { OSMO_ASSERT(ss7_initialized); LOGPAS(as, DLSS7, LOGL_INFO, "Destroying AS\n"); if (as->fi) osmo_fsm_inst_term(as->fi, OSMO_FSM_TERM_REQUEST, NULL); /* find any routes pointing to this AS and remove them */ ss7_route_table_del_routes_by_as(as->inst->rtable_system, as); as->inst = NULL; llist_del(&as->list); rate_ctr_group_free(as->ctrg); talloc_free(as); } /*! \brief Determine if given AS contains ASP * \param[in] as Application Server in which to look for \ref asp * \param[in] asp Application Server Process to look for in \ref as * \returns true in case \ref asp is part of \ref as; false otherwise */ bool osmo_ss7_as_has_asp(const struct osmo_ss7_as *as, const struct osmo_ss7_asp *asp) { unsigned int i; OSMO_ASSERT(ss7_initialized); for (i = 0; i < ARRAY_SIZE(as->cfg.asps); i++) { if (as->cfg.asps[i] == asp) return true; } return false; } /*! Determine if given AS is in the active state. * \param[in] as Application Server. * \returns true in case as is active; false otherwise. */ bool osmo_ss7_as_active(const struct osmo_ss7_as *as) { if (!as->fi) return false; return as->fi->state == XUA_AS_S_ACTIVE; } /*! Determine if given AS is in the down state. * \param[in] as Application Server. * \returns true in case as is down; false otherwise. */ bool osmo_ss7_as_down(const struct osmo_ss7_as *as) { OSMO_ASSERT(as); if (!as->fi) return true; return as->fi->state == XUA_AS_S_DOWN; } static struct osmo_ss7_asp *ss7_as_select_asp_override(struct osmo_ss7_as *as) { struct osmo_ss7_asp *asp; unsigned int i; /* FIXME: proper selection of the ASP based on the SLS! */ for (i = 0; i < ARRAY_SIZE(as->cfg.asps); i++) { asp = as->cfg.asps[i]; if (asp && osmo_ss7_asp_active(asp)) break; } return asp; } static struct osmo_ss7_asp *ss7_as_select_asp_roundrobin(struct osmo_ss7_as *as) { struct osmo_ss7_asp *asp; unsigned int i; unsigned int first_idx; first_idx = (as->cfg.last_asp_idx_sent + 1) % ARRAY_SIZE(as->cfg.asps); i = first_idx; do { asp = as->cfg.asps[i]; if (asp && osmo_ss7_asp_active(asp)) break; i = (i + 1) % ARRAY_SIZE(as->cfg.asps); } while (i != first_idx); as->cfg.last_asp_idx_sent = i; return asp; } /* returns NULL if multiple ASPs would need to be selected. */ static struct osmo_ss7_asp *ss7_as_select_asp_broadcast(struct osmo_ss7_as *as) { struct osmo_ss7_asp *asp; struct osmo_ss7_asp *asp_found = NULL; for (unsigned int i = 0; i < ARRAY_SIZE(as->cfg.asps); i++) { asp = as->cfg.asps[i]; if (!asp || !osmo_ss7_asp_active(asp)) continue; if (asp_found) /* >1 ASPs selected, early return */ return NULL; asp_found = asp; } return asp_found; } /*! Select an AS to transmit a message, according to AS configuration and ASP availability. * \param[in] as Application Server. * \returns asp to send the message to, NULL if no possible asp found * * This function returns NULL too if multiple ASPs would be selected, ie. AS is * configured in broadcast mode and more than one ASP is configured. */ struct osmo_ss7_asp *osmo_ss7_as_select_asp(struct osmo_ss7_as *as) { struct osmo_ss7_asp *asp = NULL; switch (as->cfg.mode) { case OSMO_SS7_AS_TMOD_OVERRIDE: asp = ss7_as_select_asp_override(as); break; case OSMO_SS7_AS_TMOD_LOADSHARE: /* TODO: actually use the SLS value to ensure same SLS goes * through same ASP. Not strictly required by M3UA RFC, but * would fit the overall principle. */ case OSMO_SS7_AS_TMOD_ROUNDROBIN: asp = ss7_as_select_asp_roundrobin(as); break; case OSMO_SS7_AS_TMOD_BCAST: return ss7_as_select_asp_broadcast(as); case _NUM_OSMO_SS7_ASP_TMOD: OSMO_ASSERT(false); } if (!asp) { LOGPFSM(as->fi, "No selectable ASP in AS\n"); return NULL; } return asp; } bool osmo_ss7_as_tmode_compatible_xua(struct osmo_ss7_as *as, uint32_t m3ua_tmt) { if (!as->cfg.mode_set_by_vty && !as->cfg.mode_set_by_peer) return true; switch (m3ua_tmt) { case M3UA_TMOD_OVERRIDE: if (as->cfg.mode == OSMO_SS7_AS_TMOD_OVERRIDE) return true; break; case M3UA_TMOD_LOADSHARE: if (as->cfg.mode == OSMO_SS7_AS_TMOD_LOADSHARE || as->cfg.mode == OSMO_SS7_AS_TMOD_ROUNDROBIN) return true; break; case M3UA_TMOD_BCAST: if (as->cfg.mode == OSMO_SS7_AS_TMOD_BCAST) return true; break; default: break; } return false; }