/* SCCP Instance related routines */ /* (C) 2017 by Harald Welte * All Rights Reserved * * SPDX-License-Identifier: GPL-2.0+ * * 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 . */ #include #include #include #include #include #include #include #include #include #include #include #include "sccp_connection.h" #include "sccp_internal.h" #include "sccp_instance.h" #include "sccp_user.h" #include "xua_internal.h" #include "ss7_as.h" #include "ss7_asp.h" #include "ss7_route.h" #include "ss7_route_table.h" #include "ss7_internal.h" #include "ss7_xua_srv.h" /*********************************************************************** * Timer Handling ***********************************************************************/ /* Mostly pasted from Appendix C.4 of ITU-T Q.714 (05/2001) -- some of their descriptions are quite * unintelligible out of context, for which we have our own description here. */ const struct osmo_tdef osmo_sccp_timer_defaults[OSMO_SCCP_TIMERS_LEN] = { { .T = OSMO_SCCP_TIMER_CONN_EST, .default_val = 1*60, .unit = OSMO_TDEF_S, .desc = "Waiting for connection confirm message, 1 to 2 minutes" }, { .T = OSMO_SCCP_TIMER_IAS, .default_val = 7*60, .unit = OSMO_TDEF_S, .desc = "Send keep-alive: on an idle connection, delay before sending an Idle Timer message, 5 to 10 minutes" }, /* RFC 3868 Ch. 8. */ { .T = OSMO_SCCP_TIMER_IAR, .default_val = 15*60, .unit = OSMO_TDEF_S, .desc = "Receive keep-alive: on an idle connection, delay until considering a connection as stale, 11 to 21 minutes" }, /* RFC 3868 Ch. 8. */ { .T = OSMO_SCCP_TIMER_REL, .default_val = 10, .unit = OSMO_TDEF_S, .desc = "Waiting for release complete message, 10 to 20 seconds" }, { .T = OSMO_SCCP_TIMER_REPEAT_REL, .default_val = 10, .unit = OSMO_TDEF_S, .desc = "Waiting for release complete message; or to repeat sending released message after the initial expiry, 10 to 20 seconds" }, { .T = OSMO_SCCP_TIMER_INT, .default_val = 1*60, .unit = OSMO_TDEF_S, .desc = "Waiting for release complete message; or to release connection resources, freeze the LRN and " "alert a maintenance function after the initial expiry, extending to 1 minute" }, { .T = OSMO_SCCP_TIMER_GUARD, .default_val = 23*60, .unit = OSMO_TDEF_S, .desc = "Waiting to resume normal procedure for temporary connection sections during the restart procedure, 23 to 25 minutes" }, { .T = OSMO_SCCP_TIMER_RESET, .default_val = 10, .unit = OSMO_TDEF_S, .desc = "Waiting to release temporary connection section or alert maintenance function after reset request message is sent, 10 to 20 seconds" }, { .T = OSMO_SCCP_TIMER_REASSEMBLY, .default_val = 10, .unit = OSMO_TDEF_S, .desc = "Waiting to receive all the segments of the remaining segments, single segmented message after receiving the first segment, 10 to 20 seconds" }, {} }; /* Appendix C.4 of ITU-T Q.714 */ const struct value_string osmo_sccp_timer_names[] = { { OSMO_SCCP_TIMER_CONN_EST, "conn_est" }, { OSMO_SCCP_TIMER_IAS, "ias" }, { OSMO_SCCP_TIMER_IAR, "iar" }, { OSMO_SCCP_TIMER_REL, "rel" }, { OSMO_SCCP_TIMER_REPEAT_REL, "repeat_rel" }, { OSMO_SCCP_TIMER_INT, "int" }, { OSMO_SCCP_TIMER_GUARD, "guard" }, { OSMO_SCCP_TIMER_RESET, "reset" }, { OSMO_SCCP_TIMER_REASSEMBLY, "reassembly" }, {} }; osmo_static_assert(ARRAY_SIZE(osmo_sccp_timer_defaults) == (OSMO_SCCP_TIMERS_LEN) && ARRAY_SIZE(osmo_sccp_timer_names) == (OSMO_SCCP_TIMERS_LEN), assert_osmo_sccp_timers_count); /*! \brief Find a SCCP User registered for given PC+SSN or SSN only * First search all users with a valid PC for a full PC+SSN match. * If no such match was found, search all users with an invalid PC for an SSN-only match. * \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; if (osmo_ss7_pc_is_valid(pc)) { /* First try to find match for PC + SSN */ llist_for_each_entry(scu, &inst->users, list) { if (osmo_ss7_pc_is_valid(scu->pc) && scu->pc == pc && scu->ssn == ssn) return scu; } } /* Then try to match on SSN only */ llist_for_each_entry(scu, &inst->users, list) { if (!osmo_ss7_pc_is_valid(scu->pc) && scu->ssn == ssn) return scu; } return NULL; } /*! Find a SCCP User registered for given PC+SSN or SSN only. * First search all users with a valid PC for a full PC+SSN match. * If no match was found, search all users with an invalid PC for an SSN-only match. * \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 * osmo_sccp_user_find(struct osmo_sccp_instance *inst, uint16_t ssn, uint32_t pc) { return sccp_user_find(inst, ssn, pc); } /*! \brief Bind a SCCP User to a given Point Code * \param[in] inst SCCP Instance * \param[in] name human-readable name * \param[in] prim_cb User provided callback to pass a primitive/msg up the stack * \param[in] ssn Sub-System Number to bind to * \param[in] pc Point Code to bind to, or OSMO_SS7_PC_INVALID if none. * \returns Callee-allocated SCCP User on success; negative otherwise * * Ownership of oph->msg in prim_cb is transferred to the user of the * registered callback when called. */ 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) { struct osmo_sccp_user *scu; scu = sccp_user_find(inst, ssn, pc); if (scu) { LOGPSCI(inst, LOGL_ERROR, "Cannot bind user '%s' to SSN=%u PC=%s, this SSN and PC" " is already bound by '%s'\n", name, ssn, osmo_ss7_pointcode_print(inst->ss7, pc), scu->name); return NULL; } LOGPSCI(inst, LOGL_INFO, "Binding user '%s' to SSN=%u PC=%s\n", name, ssn, osmo_ss7_pointcode_print(inst->ss7, pc)); scu = sccp_user_alloc(inst, name, prim_cb, ssn, pc); 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] prim_cb User provided callback to pass a primitive/msg up the stack * \param[in] ssn Sub-System Number to bind to * \param[in] pc Point Code to bind to * \returns Callee-allocated SCCP User on success; negative otherwise * * Ownership of oph->msg in prim_cb is transferred to the user of the * registered callback when called. */ 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); } /*! \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] prim_cb User provided callback to pass a primitive/msg up the stack * \param[in] ssn Sub-System Number to bind to * \returns Callee-allocated SCCP User on success; negative otherwise * * Ownership of oph->msg in prim_cb is transferred to the user of the * registered callback when called. */ 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, OSMO_SS7_PC_INVALID); } /* Timer cb used to transmit queued Routing Failures asynchronously up the stack */ static void rout_fail_pending_cb(void *_inst) { struct osmo_sccp_instance *inst = _inst; struct sccp_pending_rout_fail *prf; while ((prf = llist_first_entry_or_null(&inst->rout_fail_pending.queue, struct sccp_pending_rout_fail, list))) { struct xua_msg *xua = prf->xua; uint32_t cause = prf->cause; bool scoc = prf->scoc; llist_del(&prf->list); talloc_free(prf); if (scoc) /* Routing Failure SCRC -> SCOC */ sccp_scoc_rx_scrc_rout_fail(inst, xua, cause); else /* Routing Failure SCRC -> SCLC */ sccp_sclc_rx_scrc_rout_fail(inst, xua, cause); xua_msg_free(xua); } } /* Enqueue Routing Failure to submit it asynchronously to upper layers. * xua_msg is copied. * scoc: true if it's for SCOC, false if it's for SCLC. */ void sccp_rout_fail_enqueue(struct osmo_sccp_instance *inst, const struct xua_msg *xua, uint32_t cause, bool scoc) { bool queue_was_empty = llist_empty(&inst->rout_fail_pending.queue); struct sccp_pending_rout_fail *prf; LOGPSCI(inst, LOGL_DEBUG, "Enqueuing SCRC Routing Failure (%s) for %s message %s\n", osmo_sccp_return_cause_name(cause), scoc ? "CO" : "CL", xua_hdr_dump(xua, &xua_dialect_sua)); prf = talloc(inst, struct sccp_pending_rout_fail); OSMO_ASSERT(prf); *prf = (struct sccp_pending_rout_fail){ .xua = xua_msg_copy(xua), .cause = cause, .scoc = scoc, }; OSMO_ASSERT(prf->xua); llist_add_tail(&prf->list, &inst->rout_fail_pending.queue); if (queue_was_empty) osmo_timer_schedule(&inst->rout_fail_pending.timer, 0, 0); } /* 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; int rc; 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); if (!xua) { LOGPSCI(inst, LOGL_ERROR, "Couldn't convert SCCP to SUA: %s\n", msgb_hexdump(oph->msg)); rc = -1; break; } xua->mtp = omp->u.transfer; /* hand this primitive into SCCP via the SCRC code */ rc = scrc_rx_mtp_xfer_ind_xua(inst, xua); xua_msg_free(xua); break; default: LOGPSCI(inst, LOGL_ERROR, "Unknown primitive %u:%u receivd\n", oph->primitive, oph->operation); rc = -1; } msgb_free(oph->msg); return rc; } 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; int rc; inst = talloc_zero(ss7, struct osmo_sccp_instance); if (!inst) return NULL; inst->ss7 = ss7; inst->priv = priv; INIT_LLIST_HEAD(&inst->users); inst->max_optional_data = SCCP_MAX_OPTIONAL_DATA; inst->tdefs = talloc_memdup(inst, osmo_sccp_timer_defaults, sizeof(osmo_sccp_timer_defaults)); osmo_tdefs_reset(inst->tdefs); osmo_timer_setup(&inst->rout_fail_pending.timer, rout_fail_pending_cb, inst); INIT_LLIST_HEAD(&inst->rout_fail_pending.queue); rc = sccp_scmg_init(inst); if (rc < 0) { talloc_free(inst); return NULL; } inst->ss7_user = osmo_ss7_user_create(ss7, "SCCP"); osmo_ss7_user_set_prim_cb(inst->ss7_user, mtp_user_prim_cb); osmo_ss7_user_set_priv(inst->ss7_user, 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; if (!inst) return; inst->ss7->sccp = NULL; osmo_ss7_user_unregister(inst->ss7, MTP_SI_SCCP, inst->ss7_user); osmo_ss7_user_destroy(inst->ss7_user); inst->ss7_user = NULL; llist_for_each_entry_safe(scu, scu2, &inst->users, list) { osmo_sccp_user_unbind(scu); } OSMO_ASSERT(RB_EMPTY_ROOT(&inst->connections)); /* assert is empty */ osmo_timer_del(&inst->rout_fail_pending.timer); /* Note: All entries in inst->rout_fail_pending.queue are freed by talloc. */ llist_del(&inst->list); talloc_free(inst); } void osmo_sccp_set_priv(struct osmo_sccp_instance *sccp, void *priv) { sccp->priv = priv; } void *osmo_sccp_get_priv(struct osmo_sccp_instance *sccp) { return sccp->priv; } /*! \brief derive a basic local SCCP-Address from a given SCCP instance. * \param[out] dest_addr pointer to output address memory * \param[in] inst SCCP instance * \param[in] ssn Subsystem Number */ void osmo_sccp_local_addr_by_instance(struct osmo_sccp_addr *dest_addr, const struct osmo_sccp_instance *inst, uint32_t ssn) { struct osmo_ss7_instance *ss7; OSMO_ASSERT(dest_addr); OSMO_ASSERT(inst); ss7 = inst->ss7; OSMO_ASSERT(ss7); *dest_addr = (struct osmo_sccp_addr){}; osmo_sccp_make_addr_pc_ssn(dest_addr, ss7->cfg.primary_pc, ssn); } /*! \brief check whether a given SCCP-Address is consistent. * \param[in] addr SCCP address to check * \param[in] presence mask with minimum required address components * \returns true when address data seems plausible */ bool osmo_sccp_check_addr(struct osmo_sccp_addr *addr, uint32_t presence) { /* Minimum requirements do not match */ if ((addr->presence & presence) != presence) return false; /* GT ranges */ if (addr->presence & OSMO_SCCP_ADDR_T_GT) { if (addr->gt.gti > 15) return false; if (addr->gt.npi > 15) return false; if (addr->gt.nai > 127) return false; } /* Routing by GT, but no GT present */ if (addr->ri == OSMO_SCCP_RI_GT && !(addr->presence & OSMO_SCCP_ADDR_T_GT)) return false; /* Routing by PC/SSN, but no PC/SSN present */ if (addr->ri == OSMO_SCCP_RI_SSN_PC) { if ((addr->presence & OSMO_SCCP_ADDR_T_PC) == 0) return false; if ((addr->presence & OSMO_SCCP_ADDR_T_SSN) == 0) return false; } if (addr->ri == OSMO_SCCP_RI_SSN_IP) { if ((addr->presence & OSMO_SCCP_ADDR_T_IPv4) == 0 && (addr->presence & OSMO_SCCP_ADDR_T_IPv6) == 0) return false; } return true; } /*! Compare two SCCP Global Titles. * \param[in] a left side. * \param[in] b right side. * \return -1 if a < b, 1 if a > b, and 0 if a == b. */ int osmo_sccp_gt_cmp(const struct osmo_sccp_gt *a, const struct osmo_sccp_gt *b) { if (a == b) return 0; if (!a) return -1; if (!b) return 1; return memcmp(a, b, sizeof(*a)); } /*! Compare two SCCP addresses by given presence criteria. * Any OSMO_SCCP_ADDR_T_* type not set in presence_criteria is ignored. * In case all bits are set in presence_criteria, the comparison is in the order of: * OSMO_SCCP_ADDR_T_GT, OSMO_SCCP_ADDR_T_PC, OSMO_SCCP_ADDR_T_IPv4, OSMO_SCCP_ADDR_T_IPv6, OSMO_SCCP_ADDR_T_SSN. * The SCCP addresses' Routing Indicator is not compared, see osmo_sccp_addr_ri_cmp(). * \param[in] a left side. * \param[in] b right side. * \param[in] presence_criteria A bitmask of OSMO_SCCP_ADDR_T_* values, or OSMO_SCCP_ADDR_T_MASK to compare all parts, * except the routing indicator. * \return -1 if a < b, 1 if a > b, and 0 if all checked values match. */ int osmo_sccp_addr_cmp(const struct osmo_sccp_addr *a, const struct osmo_sccp_addr *b, uint32_t presence_criteria) { int rc; if (a == b) return 0; if (!a) return -1; if (!b) return 1; if (presence_criteria & OSMO_SCCP_ADDR_T_GT) { if ((a->presence & OSMO_SCCP_ADDR_T_GT) != (b->presence & OSMO_SCCP_ADDR_T_GT)) return (b->presence & OSMO_SCCP_ADDR_T_GT) ? -1 : 1; rc = osmo_sccp_gt_cmp(&a->gt, &b->gt); if (rc) return rc; } if (presence_criteria & OSMO_SCCP_ADDR_T_PC) { if ((a->presence & OSMO_SCCP_ADDR_T_PC) != (b->presence & OSMO_SCCP_ADDR_T_PC)) return (b->presence & OSMO_SCCP_ADDR_T_PC) ? -1 : 1; if ((a->presence & OSMO_SCCP_ADDR_T_PC) && a->pc != b->pc) return (a->pc < b->pc) ? -1 : 1; } if (presence_criteria & OSMO_SCCP_ADDR_T_IPv4) { if ((a->presence & OSMO_SCCP_ADDR_T_IPv4) != (b->presence & OSMO_SCCP_ADDR_T_IPv4)) return (b->presence & OSMO_SCCP_ADDR_T_IPv4) ? -1 : 1; rc = memcmp(&a->ip.v4, &b->ip.v4, sizeof(a->ip.v4)); if (rc) return rc; } if (presence_criteria & OSMO_SCCP_ADDR_T_IPv6) { if ((a->presence & OSMO_SCCP_ADDR_T_IPv6) != (b->presence & OSMO_SCCP_ADDR_T_IPv6)) return (b->presence & OSMO_SCCP_ADDR_T_IPv6) ? -1 : 1; rc = memcmp(&a->ip.v6, &b->ip.v6, sizeof(a->ip.v6)); if (rc) return rc; } if (presence_criteria & OSMO_SCCP_ADDR_T_SSN) { if ((a->presence & OSMO_SCCP_ADDR_T_SSN) != (b->presence & OSMO_SCCP_ADDR_T_SSN)) return (b->presence & OSMO_SCCP_ADDR_T_SSN) ? -1 : 1; if (a->ssn != b->ssn) return (a->ssn < b->ssn) ? -1 : 1; } return 0; } /*! Compare the routing information of two SCCP addresses. * Compare the ri of a and b, and, if equal, return osmo_sccp_addr_cmp() with presence criteria selected according to * ri. * \param[in] a left side. * \param[in] b right side. * \return -1 if a < b, 1 if a > b, and 0 if a == b. */ int osmo_sccp_addr_ri_cmp(const struct osmo_sccp_addr *a, const struct osmo_sccp_addr *b) { uint32_t presence_criteria; if (a == b) return 0; if (!a) return -1; if (!b) return 1; if (a->ri != b->ri) return (a->ri < b->ri) ? -1 : 1; switch (a->ri) { case OSMO_SCCP_RI_NONE: return 0; case OSMO_SCCP_RI_GT: presence_criteria = OSMO_SCCP_ADDR_T_GT; break; case OSMO_SCCP_RI_SSN_PC: presence_criteria = OSMO_SCCP_ADDR_T_SSN | OSMO_SCCP_ADDR_T_PC; break; case OSMO_SCCP_RI_SSN_IP: /* Pick IPv4 or v6 depending on what a->presence indicates. */ presence_criteria = OSMO_SCCP_ADDR_T_SSN | (a->presence & (OSMO_SCCP_ADDR_T_IPv4 | OSMO_SCCP_ADDR_T_IPv6)); break; default: return 0; } return osmo_sccp_addr_cmp(a, b, presence_criteria); } /*********************************************************************** * Convenience function for CLIENT ***********************************************************************/ /* Returns whether AS is already associated to any AS. * Helper function for osmo_sccp_simple_client_on_ss7_id(). */ static bool asp_serves_some_as(const struct osmo_ss7_asp *asp) { struct osmo_ss7_as *as_i; llist_for_each_entry(as_i, &asp->inst->as_list, list) { if (osmo_ss7_as_has_asp(as_i, asp)) return true; } return false; } /*! \brief request an sccp client instance * \param[in] ctx talloc context * \param[in] ss7_id of the SS7/CS7 instance * \param[in] name human readable name * \param[in] default_pc pointcode to be used on missing VTY setting * \param[in] prot protocol to be used (e.g OSMO_SS7_ASP_PROT_M3UA) * \param[in] default_local_port local port to be used on missing VTY setting * \param[in] default_local_ip local IP-address to be used on missing VTY setting (NULL: use library own defaults) * \param[in] default_remote_port remote port to be used on missing VTY setting * \param[in] default_remote_ip remote IP-address to be used on missing VTY setting (NULL: use library own defaults) * \returns callee-allocated SCCP instance on success; NULL on error */ struct osmo_sccp_instance * osmo_sccp_simple_client_on_ss7_id(void *ctx, uint32_t ss7_id, const char *name, uint32_t default_pc, enum osmo_ss7_asp_protocol prot, int default_local_port, const char *default_local_ip, int default_remote_port, const char *default_remote_ip) { struct osmo_ss7_instance *ss7; bool ss7_created = false; struct osmo_ss7_as *as; bool as_created = false; struct osmo_ss7_route *rt; bool rt_created = false; struct osmo_ss7_asp *asp; bool asp_created = false; char *as_name, *asp_name = NULL; int trans_proto; trans_proto = ss7_default_trans_proto_for_asp_proto(prot); /*! The function will examine the given CS7 instance and its sub * components (as, asp, etc.). If necessary it will allocate * the missing components. If no CS7 instance can be detected * under the caller supplied ID, a new instance will be created * beforehand. */ /* Choose default ports when the caller does not supply valid port * numbers. */ if (!default_remote_port || default_remote_port < 0) default_remote_port = osmo_ss7_asp_protocol_port(prot); if (default_local_port < 0) default_local_port = osmo_ss7_asp_protocol_port(prot); /* Check if there is already an ss7 instance present under * the given id. If not, we will create a new one. */ ss7 = osmo_ss7_instance_find(ss7_id); if (!ss7) { LOGP(DLSCCP, LOGL_NOTICE, "%s: Creating SS7 instance\n", name); /* Create a new ss7 instance */ ss7 = osmo_ss7_instance_find_or_create(ctx, ss7_id); if (!ss7) { LOGP(DLSCCP, LOGL_ERROR, "Failed to find or create SS7 instance\n"); return NULL; } /* Setup primary pointcode * NOTE: This means that the user must set the pointcode to a * proper value when a cs7 instance is defined via the VTY. */ ss7->cfg.primary_pc = default_pc; ss7_created = true; } /* In case no valid point-code has been configured via the VTY, we * will fall back to the default pointcode. */ if (!osmo_ss7_pc_is_valid(ss7->cfg.primary_pc)) { LOGP(DLSCCP, LOGL_ERROR, "SS7 instance %u: no primary point-code set, using default point-code\n", ss7->cfg.id); ss7->cfg.primary_pc = default_pc; } LOGP(DLSCCP, LOGL_NOTICE, "%s: Using SS7 instance %u, pc:%s\n", name, ss7->cfg.id, osmo_ss7_pointcode_print(ss7, ss7->cfg.primary_pc)); /* Check if there is already an application server that matches * the protocol we intend to use. If not, we will create one. */ as = osmo_ss7_as_find_by_proto(ss7, prot); if (!as) { LOGP(DLSCCP, LOGL_NOTICE, "%s: Creating AS instance\n", name); as_name = talloc_asprintf(ctx, "as-clnt-%s", name); as = osmo_ss7_as_find_or_create(ss7, as_name, prot); talloc_free(as_name); if (!as) goto out_ss7; as_created = true; as->cfg.routing_key.pc = ss7->cfg.primary_pc; as->simple_client_allocated = true; } LOGP(DLSCCP, LOGL_NOTICE, "%s: Using AS instance %s\n", name, as->cfg.name); /* Create a default dynamic route if necessary */ rt = ss7_route_table_find_route_by_dpc_mask(ss7->rtable_system, 0, 0, true); if (!rt) { LOGP(DLSCCP, LOGL_NOTICE, "%s: Creating default route\n", name); rt = ss7_route_create(ss7->rtable_system, 0, 0, true, as->cfg.name); if (!rt) goto out_as; rt_created = true; } /* Check if we do already have an application server process * that is associated with the application server we have chosen * the application server process must also match the protocol * we intend to use. */ asp = osmo_ss7_asp_find_by_proto(as, prot); if (!asp) { /* Check if the user has created an ASP for this proto that is not added on any AS yet. */ struct osmo_ss7_asp *asp_i; llist_for_each_entry(asp_i, &ss7->asp_list, list) { if (asp_i->cfg.proto != prot) continue; if (asp_serves_some_as(asp_i)) { /* This ASP is already on another AS. * If it was on this AS, we'd have found it above. */ continue; } /* This ASP matches the protocol and is not yet associated to any AS. Use it. */ asp = asp_i; LOGP(DLSCCP, LOGL_NOTICE, "%s: ASP %s for %s is not associated with any AS, using it\n", name, asp->cfg.name, osmo_ss7_asp_protocol_name(prot)); ss7_as_add_asp(as, asp); /* ASP became associated to a new AS, hence it needs to be * restarted to announce/register its Routing Context. * Make sure proper defaults are applied if app didn't * provide specific default values, then restart the ASP: */ ss7_asp_restart_after_reconfigure(asp); break; } if (!asp) { asp_name = talloc_asprintf(ctx, "asp-clnt-%s", name); LOGP(DLSCCP, LOGL_NOTICE, "%s: No unassociated ASP for %s, creating new ASP %s\n", name, osmo_ss7_asp_protocol_name(prot), asp_name); asp = osmo_ss7_asp_find_or_create2(ss7, asp_name, default_remote_port, default_local_port, trans_proto, prot); talloc_free(asp_name); if (!asp) goto out_rt; asp_created = true; asp->simple_client_allocated = true; /* Ensure that the ASP we use is set to operate as a client. */ asp->cfg.is_server = false; /* Ensure that the ASP we use is set to role ASP. */ asp->cfg.role = OSMO_SS7_ASP_ROLE_ASP; if (default_local_ip) ss7_asp_peer_set_hosts(&asp->cfg.local, asp, &default_local_ip, 1); if (default_remote_ip) ss7_asp_peer_set_hosts(&asp->cfg.remote, asp, &default_remote_ip, 1); ss7_as_add_asp(as, asp); /* Make sure proper defaults are applied if app didn't provide specific default values, then restart the ASP: */ ss7_asp_restart_after_reconfigure(asp); } } /* Extra sanity checks if the ASP asp-clnt-* was pre-configured over VTY: */ if (!asp->simple_client_allocated) { /* Forbid ASPs defined through VTY that are not entirely * configured. "role" and "transport-role" must be explicitly provided: */ if (!asp->cfg.role_set_by_vty) { LOGP(DLSCCP, LOGL_ERROR, "%s: ASP %s defined in VTY but 'role' was not set there, please set it.\n", name, asp->cfg.name); goto out_asp; } if (!asp->cfg.trans_role_set_by_vty) { LOGP(DLSCCP, LOGL_ERROR, "%s: ASP %s defined in VTY but 'transport-role' was not set there, please set it.\n", name, asp->cfg.name); goto out_asp; } /* If ASP was configured through VTY it may be explicitly configured as * SCTP server. It may be a bit confusing since this function is to create * a "SCCP simple client", but this allows users of this API such as * osmo-hnbgw to support transport-role server if properly configured through VTY. */ if (asp->cfg.is_server) { struct osmo_xua_server *xs; LOGP(DLSCCP, LOGL_NOTICE, "%s: Requesting an SCCP simple client on ASP %s configured with 'transport-role server'\n", name, asp->cfg.name); xs = ss7_xua_server_find2(ss7, asp->cfg.trans_proto, prot, asp->cfg.local.port); if (!xs) { LOGP(DLSCCP, LOGL_ERROR, "%s: Requesting an SCCP simple client on ASP %s configured " "with 'transport-role server' but no matching xUA server was configured!\n", name, asp->cfg.name); goto out_asp; } } /* ASP was already started here previously by VTY go_parent. */ } LOGP(DLSCCP, LOGL_NOTICE, "%s: Using ASP instance %s\n", name, asp->cfg.name); osmo_ss7_ensure_sccp(ss7); if (!ss7->sccp) goto out_asp; return ss7->sccp; out_asp: if (asp_created) osmo_ss7_asp_destroy(asp); out_rt: if (rt_created) ss7_route_destroy(rt); out_as: if (as_created) osmo_ss7_as_destroy(as); out_ss7: if (ss7_created) osmo_ss7_instance_destroy(ss7); return NULL; } /*! \brief request an sccp client instance * \param[in] ctx talloc context * \param[in] name human readable name * \param[in] default_pc pointcode to be used on missing VTY setting * \param[in] prot protocol to be used (e.g OSMO_SS7_ASP_PROT_M3UA) * \param[in] default_local_port local port to be used on missing VTY setting * \param[in] default_local_ip local IP-address to be used on missing VTY setting * \param[in] default_remote_port remote port to be used on missing VTY setting * \param[in] default_remote_ip remote IP-address to be used on missing VTY setting * \returns callee-allocated SCCP instance on success; NULL on error */ struct osmo_sccp_instance * osmo_sccp_simple_client(void *ctx, const char *name, uint32_t default_pc, enum osmo_ss7_asp_protocol prot, int default_local_port, const char *default_local_ip, int default_remote_port, const char *default_remote_ip) { /*! This is simplified version of osmo_sccp_simple_client_on_ss7_id(). * the only difference is that the ID of the CS7 instance will be * set to 0 statically */ return osmo_sccp_simple_client_on_ss7_id(ctx, 0, name, default_pc, prot, default_local_port, default_local_ip, default_remote_port, default_remote_ip); } /*********************************************************************** * Convenience function for SERVER ***********************************************************************/ struct osmo_sccp_instance * osmo_sccp_simple_server_on_ss7_id(void *ctx, uint32_t ss7_id, 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; int trans_proto; int rc; trans_proto = ss7_default_trans_proto_for_asp_proto(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, ss7_id); if (!ss7) return NULL; ss7->cfg.primary_pc = pc; xs = ss7_xua_server_create2(ss7, trans_proto, prot, local_port, local_ip); if (!xs) goto out_ss7; rc = ss7_xua_server_bind(xs); if (rc < 0) goto out_xs; /* Allocate SCCP stack */ osmo_ss7_ensure_sccp(ss7); if (!ss7->sccp) goto out_xs; return ss7->sccp; out_xs: ss7_xua_server_destroy(xs); out_ss7: osmo_ss7_instance_destroy(ss7); return NULL; } 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) { return osmo_sccp_simple_server_on_ss7_id(ctx, 0, pc, prot, local_port, local_ip); } 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; struct osmo_xua_server *oxs; char *as_name, *asp_name; int trans_proto; trans_proto = ss7_default_trans_proto_for_asp_proto(prot); 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; /* route only selected PC to the client */ rt = ss7_route_create(ss7->rtable_system, pc, 0xffff, true, as_name); if (!rt) goto out_as; asp = osmo_ss7_asp_find_or_create2(ss7, asp_name, remote_port, local_port, trans_proto, prot); if (!asp) goto out_rt; oxs = ss7_xua_server_find2(ss7, asp->cfg.trans_proto, prot, local_port); if (!oxs) goto out_asp; if (ss7_asp_peer_set_hosts(&asp->cfg.local, asp, (const char * const*)oxs->cfg.local.host, oxs->cfg.local.host_cnt) < 0) goto out_asp; if (ss7_asp_peer_add_host(&asp->cfg.remote, asp, remote_ip) < 0) goto out_asp; asp->cfg.is_server = true; asp->cfg.role = OSMO_SS7_ASP_ROLE_SG; ss7_as_add_asp(as, asp); talloc_free(asp_name); talloc_free(as_name); osmo_ss7_asp_restart(asp); return ss7->sccp; out_asp: osmo_ss7_asp_destroy(asp); out_rt: ss7_route_destroy(rt); out_as: osmo_ss7_as_destroy(as); out_strings: talloc_free(as_name); talloc_free(asp_name); return NULL; } /*! Adjust the upper bound for the optional data length (the payload) for CR, CC, CREF and RLSD messages. * For any Optional Data part larger than this value in octets, send CR, CC, CREF and RLSD messages without any payload, * and send the data payload in a separate Data Form 1 message. ITU-T Q.713 sections 4.2 thru 4.5 define a limit of 130 * bytes for the 'Data' parameter. This limit can be adjusted here. May be useful for interop with nonstandard SCCP * peers. * \param[in] sccp SCCP instance to reconfigure. * \param[in] val Number of bytes to set as upper bound for the optional data length, or pass a negative value to set * the standard value of SCCP_MAX_OPTIONAL_DATA == 130, which conforms to ITU-T Q.713. */ void osmo_sccp_set_max_optional_data(struct osmo_sccp_instance *inst, int val) { if (!inst) return; if (val < 0) val = SCCP_MAX_OPTIONAL_DATA; inst->max_optional_data = val; } /*! \brief get the SS7 instance that is related to the given SCCP instance * \param[in] sccp SCCP instance * \returns SS7 instance; NULL if sccp was NULL */ struct osmo_ss7_instance *osmo_sccp_get_ss7(const struct osmo_sccp_instance *sccp) { if (!sccp) return NULL; return sccp->ss7; }