/* Core SS7 Instance/Linkset/Link/AS/ASP Handling */ /* (C) 2015-2017 by Harald Welte * 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 #include #include #include #include #include #include #include #include #include #include "sccp_internal.h" #include "xua_internal.h" #include "xua_asp_fsm.h" #include "xua_as_fsm.h" #define MAX_PC_STR_LEN 32 static bool ss7_initialized = false; LLIST_HEAD(osmo_ss7_instances); static int32_t next_rctx = 1; static int32_t next_l_rk_id = 1; 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 } }; struct value_string osmo_ss7_asp_protocol_vals[] = { { OSMO_SS7_ASP_PROT_NONE, "none" }, { OSMO_SS7_ASP_PROT_SUA, "sua" }, { OSMO_SS7_ASP_PROT_M3UA, "m3ua" }, { OSMO_SS7_ASP_PROT_IPA, "ipa" }, { 0, NULL } }; #define LOGSS7(inst, level, fmt, args ...) \ LOGP(DLSS7, level, "%u: " fmt, inst ? (inst)->cfg.id : 0, ## args) static int asp_proto_to_ip_proto(enum osmo_ss7_asp_protocol proto) { switch (proto) { case OSMO_SS7_ASP_PROT_IPA: return IPPROTO_TCP; case OSMO_SS7_ASP_PROT_SUA: case OSMO_SS7_ASP_PROT_M3UA: default: return IPPROTO_SCTP; } } int osmo_ss7_find_free_rctx(struct osmo_ss7_instance *inst) { int32_t rctx; for (rctx = next_rctx; rctx; rctx = ++next_rctx) { if (!osmo_ss7_as_find_by_rctx(inst, next_rctx)) return rctx; } return -1; } static uint32_t find_free_l_rk_id(struct osmo_ss7_instance *inst) { uint32_t l_rk_id; for (l_rk_id = next_l_rk_id; next_l_rk_id; l_rk_id = ++next_l_rk_id) { if (!osmo_ss7_as_find_by_l_rk_id(inst, next_l_rk_id)) return l_rk_id; } return -1; } /*********************************************************************** * SS7 Point Code Parsing / Printing ***********************************************************************/ static const struct osmo_ss7_pc_fmt default_pc_fmt = { .delimiter = '.', .component_len = { 3, 8, 3}, }; /* like strcat() but appends a single character */ static int strnappendchar(char *str, char c, size_t n) { unsigned int curlen = strlen(str); if (n < curlen + 2) return -1; str[curlen] = c; str[curlen+1] = '\0'; return curlen+1; } /* generate a format string for formatting a point code. The result can * e.g. be used with sscanf() or sprintf() */ static const char *gen_pc_fmtstr(const struct osmo_ss7_pc_fmt *pc_fmt, unsigned int *num_comp_exp) { static char buf[MAX_PC_STR_LEN]; unsigned int num_comp = 0; buf[0] = '\0'; strcat(buf, "%u"); num_comp++; if (pc_fmt->component_len[1] == 0) goto out; strnappendchar(buf, pc_fmt->delimiter, sizeof(buf)); strcat(buf, "%u"); num_comp++; if (pc_fmt->component_len[2] == 0) goto out; strnappendchar(buf, pc_fmt->delimiter, sizeof(buf)); strcat(buf, "%u"); num_comp++; out: if (num_comp_exp) *num_comp_exp = num_comp; return buf; } /* get number of components we expect for a point code, depending on the * configuration of this ss7_instance */ static unsigned int num_pc_comp_exp(const struct osmo_ss7_pc_fmt *pc_fmt) { unsigned int num_comp_exp = 1; if (pc_fmt->component_len[1]) num_comp_exp++; if (pc_fmt->component_len[2]) num_comp_exp++; return num_comp_exp; } /* get the total width (in bits) of the point-codes in this ss7_instance */ static unsigned int get_pc_width(const struct osmo_ss7_pc_fmt *pc_fmt) { return pc_fmt->component_len[0] + pc_fmt->component_len[1] + pc_fmt->component_len[2]; } /* get the number of bits we must shift the given component of a point * code in this ss7_instance */ static unsigned int get_pc_comp_shift(const struct osmo_ss7_pc_fmt *pc_fmt, unsigned int comp_num) { uint32_t pc_width = get_pc_width(pc_fmt); switch (comp_num) { case 0: return pc_width - pc_fmt->component_len[0]; case 1: return pc_width - pc_fmt->component_len[0] - pc_fmt->component_len[1]; case 2: return 0; default: return -EINVAL; } } static uint32_t pc_comp_shift_and_mask(const struct osmo_ss7_pc_fmt *pc_fmt, unsigned int comp_num, uint32_t pc) { unsigned int shift = get_pc_comp_shift(pc_fmt, comp_num); uint32_t mask = (1 << pc_fmt->component_len[comp_num]) - 1; return (pc >> shift) & mask; } /* parse a point code according to the structure configured for this * ss7_instance */ int osmo_ss7_pointcode_parse(struct osmo_ss7_instance *inst, const char *str) { unsigned int component[3]; const struct osmo_ss7_pc_fmt *pc_fmt = inst ? &inst->cfg.pc_fmt : &default_pc_fmt; unsigned int num_comp_exp = num_pc_comp_exp(pc_fmt); const char *fmtstr = gen_pc_fmtstr(pc_fmt, &num_comp_exp); int i, rc; rc = sscanf(str, fmtstr, &component[0], &component[1], &component[2]); /* ensure all components were parsed */ if (rc != num_comp_exp) goto err; /* check none of the component values exceeds what can be * represented within its bit-width */ for (i = 0; i < num_comp_exp; i++) { if (component[i] >= (1 << pc_fmt->component_len[i])) goto err; } /* shift them all together */ rc = (component[0] << get_pc_comp_shift(pc_fmt, 0)); if (num_comp_exp > 1) rc |= (component[1] << get_pc_comp_shift(pc_fmt, 1)); if (num_comp_exp > 2) rc |= (component[2] << get_pc_comp_shift(pc_fmt, 2)); return rc; err: LOGSS7(inst, LOGL_NOTICE, "Error parsing Pointcode '%s'\n", str); return -EINVAL; } const char *_osmo_ss7_pointcode_print(char *buf, size_t len, const struct osmo_ss7_instance *inst, uint32_t pc) { const struct osmo_ss7_pc_fmt *pc_fmt; unsigned int num_comp_exp; const char *fmtstr; if (!osmo_ss7_pc_is_valid(pc)) return "(no PC)"; pc_fmt = inst ? &inst->cfg.pc_fmt : &default_pc_fmt; num_comp_exp = num_pc_comp_exp(pc_fmt); fmtstr = gen_pc_fmtstr(pc_fmt, &num_comp_exp); OSMO_ASSERT(fmtstr); snprintf(buf, len, fmtstr, pc_comp_shift_and_mask(pc_fmt, 0, pc), pc_comp_shift_and_mask(pc_fmt, 1, pc), pc_comp_shift_and_mask(pc_fmt, 2, pc)); return buf; } /* print a pointcode according to the structure configured for this * ss7_instance */ const char *osmo_ss7_pointcode_print(const struct osmo_ss7_instance *inst, uint32_t pc) { static char buf[MAX_PC_STR_LEN]; return _osmo_ss7_pointcode_print(buf, sizeof(buf), inst, pc); } /* same as osmo_ss7_pointcode_print() but using a separate buffer, useful for multiple point codes in the * same LOGP/printf. */ const char *osmo_ss7_pointcode_print2(const struct osmo_ss7_instance *inst, uint32_t pc) { static char buf[MAX_PC_STR_LEN]; return _osmo_ss7_pointcode_print(buf, sizeof(buf), inst, pc); } int osmo_ss7_pointcode_parse_mask_or_len(struct osmo_ss7_instance *inst, const char *in) { unsigned int width = get_pc_width(inst ? &inst->cfg.pc_fmt : &default_pc_fmt); if (in[0] == '/') { /* parse mask by length */ int masklen = atoi(in+1); if (masklen < 0 || masklen > 32) return -EINVAL; if (masklen == 0) return 0; return (0xFFFFFFFF << (width - masklen)) & ((1 << width)-1); } else { /* parse mask as point code */ return osmo_ss7_pointcode_parse(inst, in); } } static const uint16_t prot2port[] = { [OSMO_SS7_ASP_PROT_NONE] = 0, [OSMO_SS7_ASP_PROT_SUA] = SUA_PORT, [OSMO_SS7_ASP_PROT_M3UA] = M3UA_PORT, }; int osmo_ss7_asp_protocol_port(enum osmo_ss7_asp_protocol prot) { if (prot >= ARRAY_SIZE(prot2port)) return -EINVAL; else return prot2port[prot]; } /*********************************************************************** * SS7 Instance ***********************************************************************/ /*! \brief Find a SS7 Instance with given ID * \param[in] id ID for which to search * \returns \ref osmo_ss7_instance on success; NULL on error */ struct osmo_ss7_instance * osmo_ss7_instance_find(uint32_t id) { OSMO_ASSERT(ss7_initialized); struct osmo_ss7_instance *inst; llist_for_each_entry(inst, &osmo_ss7_instances, list) { if (inst->cfg.id == id) return inst; } return NULL; } /*! \brief Find or create a SS7 Instance * \param[in] ctx talloc allocation context to use for allocations * \param[in] id ID of SS7 Instance * \returns \ref osmo_ss7_instance on success; NULL on error */ struct osmo_ss7_instance * osmo_ss7_instance_find_or_create(void *ctx, uint32_t id) { struct osmo_ss7_instance *inst; OSMO_ASSERT(ss7_initialized); inst = osmo_ss7_instance_find(id); if (inst) return inst; inst = talloc_zero(ctx, struct osmo_ss7_instance); if (!inst) return NULL; inst->cfg.primary_pc = OSMO_SS7_PC_INVALID; inst->cfg.id = id; LOGSS7(inst, LOGL_INFO, "Creating SS7 Instance\n"); INIT_LLIST_HEAD(&inst->linksets); INIT_LLIST_HEAD(&inst->as_list); INIT_LLIST_HEAD(&inst->asp_list); INIT_LLIST_HEAD(&inst->rtable_list); INIT_LLIST_HEAD(&inst->xua_servers); inst->rtable_system = osmo_ss7_route_table_find_or_create(inst, "system"); /* default point code structure + formatting */ inst->cfg.pc_fmt.delimiter = '.'; inst->cfg.pc_fmt.component_len[0] = 3; inst->cfg.pc_fmt.component_len[1] = 8; inst->cfg.pc_fmt.component_len[2] = 3; llist_add(&inst->list, &osmo_ss7_instances); INIT_LLIST_HEAD(&inst->cfg.sccp_address_book); return inst; } /*! \brief Destroy a SS7 Instance * \param[in] inst SS7 Instance to be destroyed */ void osmo_ss7_instance_destroy(struct osmo_ss7_instance *inst) { struct osmo_ss7_linkset *lset; struct osmo_ss7_as *as; struct osmo_ss7_asp *asp; OSMO_ASSERT(ss7_initialized); LOGSS7(inst, LOGL_INFO, "Destroying SS7 Instance\n"); llist_for_each_entry(asp, &inst->asp_list, list) osmo_ss7_asp_destroy(asp); llist_for_each_entry(as, &inst->as_list, list) osmo_ss7_as_destroy(as); llist_for_each_entry(lset, &inst->linksets, list) osmo_ss7_linkset_destroy(lset); llist_del(&inst->list); talloc_free(inst); } /*! \brief Set the point code format used in given SS7 instance */ int osmo_ss7_instance_set_pc_fmt(struct osmo_ss7_instance *inst, uint8_t c0, uint8_t c1, uint8_t c2) { if (c0+c1+c2 > 32) return -EINVAL; if (c0+c1+c2 > 14) LOGSS7(inst, LOGL_NOTICE, "Point Code Format %u-%u-%u " "is longer than 14 bits, odd?\n", c0, c1, c2); inst->cfg.pc_fmt.component_len[0] = c0; inst->cfg.pc_fmt.component_len[1] = c1; inst->cfg.pc_fmt.component_len[2] = c2; return 0; } /*********************************************************************** * MTP Users (Users of MTP, such as SCCP or ISUP) ***********************************************************************/ /*! \brief Register a MTP user for a given service indicator * \param[in] inst SS7 instance for which we register the user * \param[in] service_ind Service (ISUP, SCCP, ...) * \param[in] user SS7 user (including primitive call-back) * \returns 0 on success; negative on error */ int osmo_ss7_user_register(struct osmo_ss7_instance *inst, uint8_t service_ind, struct osmo_ss7_user *user) { if (service_ind >= ARRAY_SIZE(inst->user)) return -EINVAL; if (inst->user[service_ind]) return -EBUSY; DEBUGP(DLSS7, "registering user=%s for SI %u with priv %p\n", user->name, service_ind, user->priv); user->inst = inst; inst->user[service_ind] = user; return 0; } /*! \brief Unregister a MTP user for a given service indicator * \param[in] inst SS7 instance for which we register the user * \param[in] service_ind Service (ISUP, SCCP, ...) * \param[in] user (optional) SS7 user. If present, we will not * unregister other users * \returns 0 on success; negative on error */ int osmo_ss7_user_unregister(struct osmo_ss7_instance *inst, uint8_t service_ind, struct osmo_ss7_user *user) { if (service_ind >= ARRAY_SIZE(inst->user)) return -EINVAL; if (!inst->user[service_ind]) return -ENODEV; if (user && (inst->user[service_ind] != user)) return -EINVAL; if (user) user->inst = NULL; inst->user[service_ind] = NULL; return 0; } /* deliver to a local MTP user */ int osmo_ss7_mtp_to_user(struct osmo_ss7_instance *inst, struct osmo_mtp_prim *omp) { uint32_t service_ind; const struct osmo_ss7_user *osu; if (omp->oph.sap != MTP_SAP_USER || omp->oph.primitive != OSMO_MTP_PRIM_TRANSFER || omp->oph.operation != PRIM_OP_INDICATION) { LOGP(DLSS7, LOGL_ERROR, "Unsupported Primitive\n"); return -EINVAL; } service_ind = omp->u.transfer.sio & 0xF; osu = inst->user[service_ind]; if (!osu) { LOGP(DLSS7, LOGL_NOTICE, "No MTP-User for SI %u\n", service_ind); return -ENODEV; } DEBUGP(DLSS7, "delivering MTP-TRANSFER.ind to user %s, priv=%p\n", osu->name, osu->priv); return osu->prim_cb(&omp->oph, (void *) osu->priv); } /*********************************************************************** * SS7 Linkset ***********************************************************************/ /*! \brief Destroy a SS7 Linkset * \param[in] lset Linkset to be destroyed */ void osmo_ss7_linkset_destroy(struct osmo_ss7_linkset *lset) { struct osmo_ss7_route *rt, *rt2; unsigned int i; OSMO_ASSERT(ss7_initialized); LOGSS7(lset->inst, LOGL_INFO, "Destroying Linkset %s\n", lset->cfg.name); /* find any routes pointing to this AS and remove them */ llist_for_each_entry_safe(rt, rt2, &lset->inst->rtable_system->routes, list) { if (rt->dest.linkset == lset) osmo_ss7_route_destroy(rt); } for (i = 0; i < ARRAY_SIZE(lset->links); i++) { struct osmo_ss7_link *link = lset->links[i]; if (!link) continue; osmo_ss7_link_destroy(link); } llist_del(&lset->list); talloc_free(lset); } /*! \brief Find SS7 Linkset by given name * \param[in] inst SS7 Instance in which to look * \param[in] name Name of SS7 Linkset * \returns pointer to linkset on success; NULL on error */ struct osmo_ss7_linkset * osmo_ss7_linkset_find_by_name(struct osmo_ss7_instance *inst, const char *name) { struct osmo_ss7_linkset *lset; OSMO_ASSERT(ss7_initialized); llist_for_each_entry(lset, &inst->linksets, list) { if (!strcmp(name, lset->cfg.name)) return lset; } return NULL; } /*! \brief Find or allocate SS7 Linkset * \param[in] inst SS7 Instance in which we operate * \param[in] name Name of SS7 Linkset * \param[in] pc Adjacent Pointcode * \returns pointer to Linkset on success; NULL on error */ struct osmo_ss7_linkset * osmo_ss7_linkset_find_or_create(struct osmo_ss7_instance *inst, const char *name, uint32_t pc) { struct osmo_ss7_linkset *lset; OSMO_ASSERT(ss7_initialized); lset = osmo_ss7_linkset_find_by_name(inst, name); if (lset && lset->cfg.adjacent_pc != pc) return NULL; if (!lset) { LOGSS7(inst, LOGL_INFO, "Creating Linkset %s\n", name); lset = talloc_zero(inst, struct osmo_ss7_linkset); lset->inst = inst; lset->cfg.adjacent_pc = pc; lset->cfg.name = talloc_strdup(lset, name); llist_add_tail(&lset->list, &inst->linksets); } return lset; } /*********************************************************************** * SS7 Link ***********************************************************************/ /*! \brief Destryo SS7 Link * \param[in] link SS7 Link to be destroyed */ void osmo_ss7_link_destroy(struct osmo_ss7_link *link) { struct osmo_ss7_linkset *lset = link->linkset; OSMO_ASSERT(ss7_initialized); LOGSS7(lset->inst, LOGL_INFO, "Destroying Link %s:%u\n", lset->cfg.name, link->cfg.id); /* FIXME: do cleanup */ lset->links[link->cfg.id] = NULL; talloc_free(link); } /*! \brief Find or create SS7 Link with given ID in given Linkset * \param[in] lset SS7 Linkset on which we operate * \param[in] id Link number within Linkset * \returns pointer to SS7 Link on success; NULL on error */ struct osmo_ss7_link * osmo_ss7_link_find_or_create(struct osmo_ss7_linkset *lset, uint32_t id) { struct osmo_ss7_link *link; OSMO_ASSERT(ss7_initialized); if (id >= ARRAY_SIZE(lset->links)) return NULL; if (lset->links[id]) { link = lset->links[id]; } else { LOGSS7(lset->inst, LOGL_INFO, "Creating Link %s:%u\n", lset->cfg.name, id); link = talloc_zero(lset, struct osmo_ss7_link); if (!link) return NULL; link->linkset = lset; lset->links[id] = link; link->cfg.id = id; } return link; } /*********************************************************************** * SS7 Route Tables ***********************************************************************/ struct osmo_ss7_route_table * osmo_ss7_route_table_find(struct osmo_ss7_instance *inst, const char *name) { struct osmo_ss7_route_table *rtbl; OSMO_ASSERT(ss7_initialized); llist_for_each_entry(rtbl, &inst->rtable_list, list) { if (!strcmp(rtbl->cfg.name, name)) return rtbl; } return NULL; } struct osmo_ss7_route_table * osmo_ss7_route_table_find_or_create(struct osmo_ss7_instance *inst, const char *name) { struct osmo_ss7_route_table *rtbl; OSMO_ASSERT(ss7_initialized); rtbl = osmo_ss7_route_table_find(inst, name); if (!rtbl) { LOGSS7(inst, LOGL_INFO, "Creating Route Table %s\n", name); rtbl = talloc_zero(inst, struct osmo_ss7_route_table); rtbl->inst = inst; rtbl->cfg.name = talloc_strdup(rtbl, name); INIT_LLIST_HEAD(&rtbl->routes); llist_add_tail(&rtbl->list, &inst->rtable_list); } return rtbl; } void osmo_ss7_route_table_destroy(struct osmo_ss7_route_table *rtbl) { llist_del(&rtbl->list); /* routes are allocated as children of route table, will be * automatically freed() */ talloc_free(rtbl); } /*********************************************************************** * SS7 Routes ***********************************************************************/ /*! \brief Find a SS7 route for given destination point code in given table */ struct osmo_ss7_route * osmo_ss7_route_find_dpc(struct osmo_ss7_route_table *rtbl, uint32_t dpc) { struct osmo_ss7_route *rt; OSMO_ASSERT(ss7_initialized); /* we assume the routes are sorted by mask length, i.e. more * specific routes first, and less specific routes with shorter * mask later */ llist_for_each_entry(rt, &rtbl->routes, list) { if ((dpc & rt->cfg.mask) == rt->cfg.pc) return rt; } return NULL; } /*! \brief Find a SS7 route for given destination point code + mask in given table */ struct osmo_ss7_route * osmo_ss7_route_find_dpc_mask(struct osmo_ss7_route_table *rtbl, uint32_t dpc, uint32_t mask) { struct osmo_ss7_route *rt; OSMO_ASSERT(ss7_initialized); /* we assume the routes are sorted by mask length, i.e. more * specific routes first, and less specific routes with shorter * mask later */ llist_for_each_entry(rt, &rtbl->routes, list) { if (dpc == rt->cfg.pc && mask == rt->cfg.mask) return rt; } return NULL; } /*! \brief Find a SS7 route for given destination point code in given SS7 */ struct osmo_ss7_route * osmo_ss7_route_lookup(struct osmo_ss7_instance *inst, uint32_t dpc) { OSMO_ASSERT(ss7_initialized); return osmo_ss7_route_find_dpc(inst->rtable_system, dpc); } /* insert the route in the ordered list of routes. The list is sorted by * mask length, so that the more specific (longer mask) routes are * first, while the less specific routes with shorter masks are last. * Hence, the first matching route in a linear iteration is the most * specific match. */ static void route_insert_sorted(struct osmo_ss7_route_table *rtbl, struct osmo_ss7_route *cmp) { struct osmo_ss7_route *rt; llist_for_each_entry(rt, &rtbl->routes, list) { if (rt->cfg.mask < cmp->cfg.mask) { /* insert before the current entry */ llist_add(&cmp->list, rt->list.prev); return; } } /* not added, i.e. no smaller mask length found: we are the * smallest mask and thus should go last */ llist_add_tail(&cmp->list, &rtbl->routes); } /*! \brief Create a new route in the given routing table * \param[in] rtbl Routing Table in which the route is to be created * \param[in] pc Point Code of the destination of the route * \param[in] mask Mask of the destination Point Code \ref pc * \param[in] linkset_name string name of the linkset to be used * \returns caller-allocated + initialized route, NULL on error */ struct osmo_ss7_route * osmo_ss7_route_create(struct osmo_ss7_route_table *rtbl, uint32_t pc, uint32_t mask, const char *linkset_name) { struct osmo_ss7_route *rt; struct osmo_ss7_linkset *lset; struct osmo_ss7_as *as = NULL; OSMO_ASSERT(ss7_initialized); lset = osmo_ss7_linkset_find_by_name(rtbl->inst, linkset_name); if (!lset) { as = osmo_ss7_as_find_by_name(rtbl->inst, linkset_name); if (!as) return NULL; } rt = talloc_zero(rtbl, struct osmo_ss7_route); if (!rt) return NULL; rt->cfg.pc = pc; rt->cfg.mask = mask; rt->cfg.linkset_name = talloc_strdup(rt, linkset_name); if (lset) { rt->dest.linkset = lset; LOGSS7(rtbl->inst, LOGL_INFO, "Creating route: pc=%u=%s mask=0x%x via linkset '%s'\n", pc, osmo_ss7_pointcode_print(rtbl->inst, pc), mask, lset->cfg.name); } else { rt->dest.as = as; LOGSS7(rtbl->inst, LOGL_INFO, "Creating route: pc=%u=%s mask=0x%x via AS '%s'\n", pc, osmo_ss7_pointcode_print(rtbl->inst, pc), mask, as->cfg.name); } rt->rtable = rtbl; route_insert_sorted(rtbl, rt); return rt; } /*! \brief Destroy a given SS7 route */ void osmo_ss7_route_destroy(struct osmo_ss7_route *rt) { OSMO_ASSERT(ss7_initialized); llist_del(&rt->list); talloc_free(rt); } /*********************************************************************** * SS7 Application Server ***********************************************************************/ /*! \brief Find Application Server by given name * \param[in] inst SS7 Instance on which we operate * \param[in] name Name of AS * \returns pointer to Application Server on success; NULL otherwise */ struct osmo_ss7_as * osmo_ss7_as_find_by_name(struct osmo_ss7_instance *inst, const char *name) { struct osmo_ss7_as *as; OSMO_ASSERT(ss7_initialized); llist_for_each_entry(as, &inst->as_list, list) { if (!strcmp(name, as->cfg.name)) return as; } return NULL; } /*! \brief Find Application Server by given routing context * \param[in] inst SS7 Instance on which we operate * \param[in] rctx Routing Context * \returns pointer to Application Server on success; NULL otherwise */ struct osmo_ss7_as * osmo_ss7_as_find_by_rctx(struct osmo_ss7_instance *inst, uint32_t rctx) { struct osmo_ss7_as *as; OSMO_ASSERT(ss7_initialized); llist_for_each_entry(as, &inst->as_list, list) { if (as->cfg.routing_key.context == rctx) return as; } return NULL; } /*! \brief Find Application Server by given local routing key ID * \param[in] inst SS7 Instance on which we operate * \param[in] l_rk_id Local Routing Key ID * \returns pointer to Application Server on success; NULL otherwise */ struct osmo_ss7_as * osmo_ss7_as_find_by_l_rk_id(struct osmo_ss7_instance *inst, uint32_t l_rk_id) { struct osmo_ss7_as *as; OSMO_ASSERT(ss7_initialized); llist_for_each_entry(as, &inst->as_list, list) { if (as->cfg.routing_key.l_rk_id == l_rk_id) return as; } return NULL; } /*! \brief Find Application Server (AS) by given protocol. * \param[in] inst SS7 Instance on which we operate * \param[in] proto Protocol identifier that must match * \returns pointer to AS on success; NULL otherwise * If an AS has an ASP also matching the given protocol, that AS is preferred. * If there are multiple matches, return the first matching AS. */ struct osmo_ss7_as *osmo_ss7_as_find_by_proto(struct osmo_ss7_instance *inst, enum osmo_ss7_asp_protocol proto) { struct osmo_ss7_as *as; struct osmo_ss7_as *as_without_asp = NULL; OSMO_ASSERT(ss7_initialized); /* Loop through the list with AS and try to find one where the proto matches up */ llist_for_each_entry(as, &inst->as_list, list) { if (as->cfg.proto == proto) { /* Put down the first AS that matches the proto, just in * case we will not find any matching ASP */ if (!as_without_asp) as_without_asp = as; /* Check if the candicate we have here has any suitable * ASP */ if (osmo_ss7_asp_find_by_proto(as, proto)) return as; } } /* Return with the second best find, if there is any */ return as_without_asp; } /*! \brief Find or Create 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 suuccess; NULL otherwise */ struct osmo_ss7_as * osmo_ss7_as_find_or_create(struct osmo_ss7_instance *inst, const char *name, enum osmo_ss7_asp_protocol proto) { struct osmo_ss7_as *as; OSMO_ASSERT(ss7_initialized); as = osmo_ss7_as_find_by_name(inst, name); if (as && as->cfg.proto != proto) return NULL; if (!as) { LOGSS7(inst, LOGL_INFO, "Creating AS %s\n", name); as = talloc_zero(inst, struct osmo_ss7_as); if (!as) return NULL; as->inst = inst; as->cfg.name = talloc_strdup(as, name); as->cfg.proto = proto; as->cfg.mode = OSMO_SS7_AS_TMOD_LOADSHARE; as->cfg.recovery_timeout_msec = 2000; as->cfg.routing_key.l_rk_id = 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 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; LOGSS7(as->inst, LOGL_INFO, "Adding ASP %s to AS %s\n", asp->cfg.name, as->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; 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; LOGSS7(as->inst, LOGL_INFO, "Removing ASP %s from AS %s\n", asp->cfg.name, as->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) { struct osmo_ss7_route *rt, *rt2; OSMO_ASSERT(ss7_initialized); LOGSS7(as->inst, LOGL_INFO, "Destroying AS %s\n", as->cfg.name); if (as->fi) osmo_fsm_inst_term(as->fi, OSMO_FSM_TERM_REQUEST, NULL); /* find any routes pointing to this AS and remove them */ llist_for_each_entry_safe(rt, rt2, &as->inst->rtable_system->routes, list) { if (rt->dest.as == as) osmo_ss7_route_destroy(rt); } as->inst = NULL; llist_del(&as->list); 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(struct osmo_ss7_as *as, 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; } /*********************************************************************** * SS7 Application Server Process ***********************************************************************/ struct osmo_ss7_asp * osmo_ss7_asp_find_by_name(struct osmo_ss7_instance *inst, const char *name) { struct osmo_ss7_asp *asp; OSMO_ASSERT(ss7_initialized); llist_for_each_entry(asp, &inst->asp_list, list) { if (!strcmp(name, asp->cfg.name)) return asp; } return NULL; } static uint16_t get_in_port(struct sockaddr *sa) { switch (sa->sa_family) { case AF_INET: return (((struct sockaddr_in*)sa)->sin_port); case AF_INET6: return (((struct sockaddr_in6*)sa)->sin6_port); default: return 0; } } /*! \brief Find an ASP definition matching the local+remote IP/PORT of given fd * \param[in] fd socket descriptor of given socket * \returns SS7 ASP in case a matching one is found; NULL otherwise */ static struct osmo_ss7_asp * osmo_ss7_asp_find_by_socket_addr(int fd) { struct osmo_ss7_instance *inst; struct sockaddr sa_l, sa_r; socklen_t sa_len_l = sizeof(sa_l); socklen_t sa_len_r = sizeof(sa_r); char hostbuf_l[64], hostbuf_r[64]; uint16_t local_port, remote_port; int rc; OSMO_ASSERT(ss7_initialized); /* convert local and remote IP to string */ rc = getsockname(fd, &sa_l, &sa_len_l); if (rc < 0) return NULL; rc = getnameinfo(&sa_l, sa_len_l, hostbuf_l, sizeof(hostbuf_l), NULL, 0, NI_NUMERICHOST); if (rc < 0) return NULL; local_port = ntohs(get_in_port(&sa_l)); rc = getpeername(fd, &sa_r, &sa_len_r); if (rc < 0) return NULL; rc = getnameinfo(&sa_r, sa_len_r, hostbuf_r, sizeof(hostbuf_r), NULL, 0, NI_NUMERICHOST); if (rc < 0) return NULL; remote_port = ntohs(get_in_port(&sa_r)); /* check all instances for any ASP definition matching the * address combination of local/remote ip/port */ llist_for_each_entry(inst, &osmo_ss7_instances, list) { struct osmo_ss7_asp *asp; llist_for_each_entry(asp, &inst->asp_list, list) { if (asp->cfg.local.port == local_port && (!asp->cfg.remote.port ||asp->cfg.remote.port == remote_port) && (!asp->cfg.local.host || !strcmp(asp->cfg.local.host, hostbuf_l)) && (!asp->cfg.remote.host || !strcmp(asp->cfg.remote.host, hostbuf_r))) return asp; } } return NULL; } /*! \brief Find an ASP that matches the given protocol. * \param[in] as Application Server in which to look for \ref asp * \returns SS7 ASP in case a matching one is found; NULL otherwise */ struct osmo_ss7_asp *osmo_ss7_asp_find_by_proto(struct osmo_ss7_as *as, enum osmo_ss7_asp_protocol proto) { unsigned int i; for (i = 0; i < ARRAY_SIZE(as->cfg.asps); i++) { if (as->cfg.asps[i] && as->cfg.asps[i]->cfg.proto == proto) return as->cfg.asps[i]; } return NULL; } struct osmo_ss7_asp * osmo_ss7_asp_find_or_create(struct osmo_ss7_instance *inst, const char *name, uint16_t remote_port, uint16_t local_port, enum osmo_ss7_asp_protocol proto) { struct osmo_ss7_asp *asp; OSMO_ASSERT(ss7_initialized); asp = osmo_ss7_asp_find_by_name(inst, name); if (asp && (asp->cfg.remote.port != remote_port || asp->cfg.local.port != local_port || asp->cfg.proto != proto)) return NULL; if (!asp) { /* FIXME: check if local port has SCTP? */ asp = talloc_zero(inst, struct osmo_ss7_asp); asp->inst = inst; asp->cfg.remote.port = remote_port; asp->cfg.local.port = local_port; asp->cfg.proto = proto; asp->cfg.name = talloc_strdup(asp, name); llist_add_tail(&asp->list, &inst->asp_list); /* The SUA code internally needs SCCP to work */ if (proto == OSMO_SS7_ASP_PROT_SUA && !inst->sccp) inst->sccp = osmo_sccp_instance_create(inst, NULL); } return asp; } void osmo_ss7_asp_destroy(struct osmo_ss7_asp *asp) { struct osmo_ss7_as *as; OSMO_ASSERT(ss7_initialized); LOGSS7(asp->inst, LOGL_INFO, "Destroying ASP %s\n", asp->cfg.name); if (asp->server) osmo_stream_srv_destroy(asp->server); if (asp->client) osmo_stream_cli_destroy(asp->client); if (asp->fi) osmo_fsm_inst_term(asp->fi, OSMO_FSM_TERM_REQUEST, NULL); if (asp->xua_server) llist_del(&asp->siblings); /* unlink from all ASs we are part of */ llist_for_each_entry(as, &asp->inst->as_list, list) { unsigned int i; for (i = 0; i < ARRAY_SIZE(as->cfg.asps); i++) { if (as->cfg.asps[i] == asp) { as->cfg.asps[i] = NULL; } } } /* unlink from ss7_instance */ asp->inst = NULL; llist_del(&asp->list); /* release memory */ talloc_free(asp); } static int xua_cli_read_cb(struct osmo_stream_cli *conn); static int ipa_cli_read_cb(struct osmo_stream_cli *conn); static int xua_cli_connect_cb(struct osmo_stream_cli *cli); int osmo_ss7_asp_restart(struct osmo_ss7_asp *asp) { int rc; enum xua_asp_role role; OSMO_ASSERT(ss7_initialized); LOGSS7(asp->inst, LOGL_INFO, "Restarting ASP %s\n", asp->cfg.name); if (!asp->cfg.is_server) { /* We are in client mode now */ if (asp->server) { /* if we previously were in server mode, * destroy it */ osmo_stream_srv_destroy(asp->server); asp->server = NULL; } if (!asp->client) asp->client = osmo_stream_cli_create(asp); if (!asp->client) { LOGSS7(asp->inst, LOGL_ERROR, "Unable to create stream" " client for ASP %s\n", asp->cfg.name); return -1; } osmo_stream_cli_set_nodelay(asp->client, true); osmo_stream_cli_set_addr(asp->client, asp->cfg.remote.host); osmo_stream_cli_set_port(asp->client, asp->cfg.remote.port); osmo_stream_cli_set_local_addr(asp->client, asp->cfg.local.host); osmo_stream_cli_set_local_port(asp->client, asp->cfg.local.port); osmo_stream_cli_set_proto(asp->client, asp_proto_to_ip_proto(asp->cfg.proto)); osmo_stream_cli_set_reconnect_timeout(asp->client, 5); osmo_stream_cli_set_connect_cb(asp->client, xua_cli_connect_cb); if (asp->cfg.proto == OSMO_SS7_ASP_PROT_IPA) osmo_stream_cli_set_read_cb(asp->client, ipa_cli_read_cb); else osmo_stream_cli_set_read_cb(asp->client, xua_cli_read_cb); osmo_stream_cli_set_data(asp->client, asp); rc = osmo_stream_cli_open2(asp->client, 1); if (rc < 0) { LOGSS7(asp->inst, LOGL_ERROR, "Unable to open stream" " client for ASP %s\n", asp->cfg.name); } /* TODO: make this configurable and not implicit */ role = XUA_ASPFSM_ROLE_ASP; } else { /* We are in server mode now */ if (asp->client) { /* if we previously were in client mode, * destroy it */ osmo_stream_cli_destroy(asp->client); asp->client = NULL; } /* FIXME: ensure we have a SCTP server */ LOGSS7(asp->inst, LOGL_NOTICE, "ASP Restart for server " "not implemented yet!\n"); /* TODO: make this configurable and not implicit */ role = XUA_ASPFSM_ROLE_SG; } /* (re)start the ASP FSM */ if (asp->fi) osmo_fsm_inst_term(asp->fi, OSMO_FSM_TERM_REQUEST, NULL); asp->fi = xua_asp_fsm_start(asp, role, LOGL_DEBUG); return 0; } /*********************************************************************** * libosmo-netif integration for SCTP stream server/client ***********************************************************************/ static const struct value_string sctp_assoc_chg_vals[] = { { SCTP_COMM_UP, "COMM_UP" }, { SCTP_COMM_LOST, "COMM_LOST" }, { SCTP_RESTART, "RESTART" }, { SCTP_SHUTDOWN_COMP, "SHUTDOWN_COMP" }, { SCTP_CANT_STR_ASSOC, "CANT_STR_ASSOC" }, { 0, NULL } }; static const struct value_string sctp_sn_type_vals[] = { { SCTP_ASSOC_CHANGE, "ASSOC_CHANGE" }, { SCTP_PEER_ADDR_CHANGE, "PEER_ADDR_CHANGE" }, { SCTP_SHUTDOWN_EVENT, "SHUTDOWN_EVENT" }, { SCTP_SEND_FAILED, "SEND_FAILED" }, { SCTP_REMOTE_ERROR, "REMOTE_ERROR" }, { SCTP_PARTIAL_DELIVERY_EVENT, "PARTIAL_DELIVERY_EVENT" }, { SCTP_ADAPTATION_INDICATION, "ADAPTATION_INDICATION" }, #ifdef SCTP_AUTHENTICATION_INDICATION { SCTP_AUTHENTICATION_INDICATION, "UTHENTICATION_INDICATION" }, #endif #ifdef SCTP_SENDER_DRY_EVENT { SCTP_SENDER_DRY_EVENT, "SENDER_DRY_EVENT" }, #endif { 0, NULL } }; static int get_logevel_by_sn_type(int sn_type) { switch (sn_type) { case SCTP_ADAPTATION_INDICATION: case SCTP_PEER_ADDR_CHANGE: #ifdef SCTP_AUTHENTICATION_INDICATION case SCTP_AUTHENTICATION_INDICATION: #endif #ifdef SCTP_SENDER_DRY_EVENT case SCTP_SENDER_DRY_EVENT: #endif return LOGL_INFO; case SCTP_ASSOC_CHANGE: return LOGL_NOTICE; case SCTP_SHUTDOWN_EVENT: case SCTP_PARTIAL_DELIVERY_EVENT: return LOGL_NOTICE; case SCTP_SEND_FAILED: case SCTP_REMOTE_ERROR: return LOGL_ERROR; default: return LOGL_NOTICE; } } static void log_sctp_notification(struct osmo_ss7_asp *asp, const char *pfx, union sctp_notification *notif) { int log_level; LOGPASP(asp, DLSS7, LOGL_INFO, "%s SCTP NOTIFICATION %u flags=0x%0x\n", pfx, notif->sn_header.sn_type, notif->sn_header.sn_flags); log_level = get_logevel_by_sn_type(notif->sn_header.sn_type); switch (notif->sn_header.sn_type) { case SCTP_ASSOC_CHANGE: LOGPASP(asp, DLSS7, log_level, "%s SCTP_ASSOC_CHANGE: %s\n", pfx, get_value_string(sctp_assoc_chg_vals, notif->sn_assoc_change.sac_state)); break; default: LOGPASP(asp, DLSS7, log_level, "%s %s\n", pfx, get_value_string(sctp_sn_type_vals, notif->sn_header.sn_type)); break; } } /* netif code tells us we can read something from the socket */ static int ipa_srv_conn_cb(struct osmo_stream_srv *conn) { struct osmo_fd *ofd = osmo_stream_srv_get_ofd(conn); struct osmo_ss7_asp *asp = osmo_stream_srv_get_data(conn); struct msgb *msg = NULL; int rc; /* read IPA message from socket and process it */ rc = ipa_msg_recv_buffered(ofd->fd, &msg, &asp->pending_msg); LOGPASP(asp, DLSS7, LOGL_DEBUG, "%s(): ipa_msg_recv_buffered() returned %d\n", __func__, rc); if (rc <= 0) { if (rc == -EAGAIN) { /* more data needed */ return 0; } osmo_stream_srv_destroy(conn); return rc; } if (osmo_ipa_process_msg(msg) < 0) { LOGPASP(asp, DLSS7, LOGL_ERROR, "Bad IPA message\n"); osmo_stream_srv_destroy(conn); msgb_free(msg); return -1; } msg->dst = asp; return ipa_rx_msg(asp, msg); } /* netif code tells us we can read something from the socket */ static int xua_srv_conn_cb(struct osmo_stream_srv *conn) { struct osmo_fd *ofd = osmo_stream_srv_get_ofd(conn); struct osmo_ss7_asp *asp = osmo_stream_srv_get_data(conn); struct msgb *msg = m3ua_msgb_alloc("xUA Server Rx"); struct sctp_sndrcvinfo sinfo; unsigned int ppid; int flags = 0; int rc; if (!msg) return -ENOMEM; /* read xUA message from socket and process it */ rc = sctp_recvmsg(ofd->fd, msgb_data(msg), msgb_tailroom(msg), NULL, NULL, &sinfo, &flags); LOGPASP(asp, DLSS7, LOGL_DEBUG, "%s(): sctp_recvmsg() returned %d (flags=0x%x)\n", __func__, rc, flags); if (rc < 0) { osmo_stream_srv_destroy(conn); goto out; } else if (rc == 0) { osmo_stream_srv_destroy(conn); goto out; } else { msgb_put(msg, rc); } if (flags & MSG_NOTIFICATION) { union sctp_notification *notif = (union sctp_notification *) msgb_data(msg); log_sctp_notification(asp, "xUA SRV", notif); switch (notif->sn_header.sn_type) { case SCTP_SHUTDOWN_EVENT: osmo_stream_srv_destroy(conn); break; case SCTP_ASSOC_CHANGE: if (notif->sn_assoc_change.sac_state == SCTP_RESTART) xua_asp_send_xlm_prim_simple(asp, OSMO_XLM_PRIM_M_SCTP_RESTART, PRIM_OP_INDICATION); break; default: break; } rc = 0; goto out; } ppid = ntohl(sinfo.sinfo_ppid); msgb_sctp_ppid(msg) = ppid; msgb_sctp_stream(msg) = sinfo.sinfo_stream; msg->dst = asp; if (ppid == SUA_PPID && asp->cfg.proto == OSMO_SS7_ASP_PROT_SUA) rc = sua_rx_msg(asp, msg); else if (ppid == M3UA_PPID && asp->cfg.proto == OSMO_SS7_ASP_PROT_M3UA) rc = m3ua_rx_msg(asp, msg); else { LOGPASP(asp, DLSS7, LOGL_NOTICE, "SCTP chunk for unknown PPID %u " "received\n", ppid); rc = 0; } out: msgb_free(msg); return rc; } /* client has established SCTP connection to server */ static int xua_cli_connect_cb(struct osmo_stream_cli *cli) { struct osmo_fd *ofd = osmo_stream_cli_get_ofd(cli); struct osmo_ss7_asp *asp = osmo_stream_cli_get_data(cli); /* update the socket name */ if (asp->sock_name) talloc_free(asp->sock_name); asp->sock_name = osmo_sock_get_name(asp, ofd->fd); LOGPASP(asp, DLSS7, LOGL_INFO, "Client connected %s\n", asp->sock_name); if (asp->lm && asp->lm->prim_cb) { /* Notify layer manager that a connection has been * established */ xua_asp_send_xlm_prim_simple(asp, OSMO_XLM_PRIM_M_SCTP_ESTABLISH, PRIM_OP_INDICATION); } else { /* directly as the ASP FSM to start by sending an ASP-UP ... */ osmo_fsm_inst_dispatch(asp->fi, XUA_ASP_E_M_ASP_UP_REQ, NULL); } return 0; } static void xua_cli_close(struct osmo_stream_cli *cli) { struct osmo_ss7_asp *asp = osmo_stream_cli_get_data(cli); osmo_stream_cli_close(cli); osmo_fsm_inst_dispatch(asp->fi, XUA_ASP_E_SCTP_COMM_DOWN_IND, asp); /* send M-SCTP_RELEASE.ind to XUA Layer Manager */ xua_asp_send_xlm_prim_simple(asp, OSMO_XLM_PRIM_M_SCTP_RELEASE, PRIM_OP_INDICATION); } static void xua_cli_close_and_reconnect(struct osmo_stream_cli *cli) { xua_cli_close(cli); osmo_stream_cli_reconnect(cli); } /* read call-back for IPA/SCCPlite socket */ static int ipa_cli_read_cb(struct osmo_stream_cli *conn) { struct osmo_fd *ofd = osmo_stream_cli_get_ofd(conn); struct osmo_ss7_asp *asp = osmo_stream_cli_get_data(conn); struct msgb *msg = NULL; int rc; /* read IPA message from socket and process it */ rc = ipa_msg_recv_buffered(ofd->fd, &msg, &asp->pending_msg); LOGPASP(asp, DLSS7, LOGL_DEBUG, "%s(): ipa_msg_recv_buffered() returned %d\n", __func__, rc); if (rc <= 0) { if (rc == -EAGAIN) { /* more data needed */ return 0; } xua_cli_close_and_reconnect(conn); return rc; } if (osmo_ipa_process_msg(msg) < 0) { LOGPASP(asp, DLSS7, LOGL_ERROR, "Bad IPA message\n"); xua_cli_close_and_reconnect(conn); msgb_free(msg); return -1; } msg->dst = asp; return ipa_rx_msg(asp, msg); } static int xua_cli_read_cb(struct osmo_stream_cli *conn) { struct osmo_fd *ofd = osmo_stream_cli_get_ofd(conn); struct osmo_ss7_asp *asp = osmo_stream_cli_get_data(conn); struct msgb *msg = m3ua_msgb_alloc("xUA Client Rx"); struct sctp_sndrcvinfo sinfo; unsigned int ppid; int flags = 0; int rc; if (!msg) return -ENOMEM; /* read xUA message from socket and process it */ rc = sctp_recvmsg(ofd->fd, msgb_data(msg), msgb_tailroom(msg), NULL, NULL, &sinfo, &flags); LOGPASP(asp, DLSS7, LOGL_DEBUG, "%s(): sctp_recvmsg() returned %d (flags=0x%x)\n", __func__, rc, flags); if (rc < 0) { xua_cli_close_and_reconnect(conn); goto out; } else if (rc == 0) { xua_cli_close_and_reconnect(conn); } else { msgb_put(msg, rc); } if (flags & MSG_NOTIFICATION) { union sctp_notification *notif = (union sctp_notification *) msgb_data(msg); log_sctp_notification(asp, "xUA CLNT", notif); switch (notif->sn_header.sn_type) { case SCTP_SHUTDOWN_EVENT: xua_cli_close_and_reconnect(conn); break; case SCTP_ASSOC_CHANGE: if (notif->sn_assoc_change.sac_state == SCTP_RESTART) xua_asp_send_xlm_prim_simple(asp, OSMO_XLM_PRIM_M_SCTP_RESTART, PRIM_OP_INDICATION); default: break; } rc = 0; goto out; } if (rc == 0) goto out; ppid = ntohl(sinfo.sinfo_ppid); msgb_sctp_ppid(msg) = ppid; msgb_sctp_stream(msg) = sinfo.sinfo_stream; msg->dst = asp; if (ppid == SUA_PPID && asp->cfg.proto == OSMO_SS7_ASP_PROT_SUA) rc = sua_rx_msg(asp, msg); else if (ppid == M3UA_PPID && asp->cfg.proto == OSMO_SS7_ASP_PROT_M3UA) rc = m3ua_rx_msg(asp, msg); else { LOGPASP(asp, DLSS7, LOGL_NOTICE, "SCTP chunk for unknown PPID %u " "received\n", ppid); rc = 0; } out: msgb_free(msg); return rc; } static int xua_srv_conn_closed_cb(struct osmo_stream_srv *srv) { struct osmo_ss7_asp *asp = osmo_stream_srv_get_data(srv); LOGP(DLSS7, LOGL_INFO, "%s: SCTP connection closed\n", asp ? asp->cfg.name : "?"); if (!asp) return 0; /* notify ASP FSM and everyone else */ osmo_fsm_inst_dispatch(asp->fi, XUA_ASP_E_SCTP_COMM_DOWN_IND, NULL); /* delete any RKM-dynamically allocated ASs for this ASP */ xua_rkm_cleanup_dyn_as_for_asp(asp); /* send M-SCTP_RELEASE.ind to Layer Manager */ xua_asp_send_xlm_prim_simple(asp, OSMO_XLM_PRIM_M_SCTP_RELEASE, PRIM_OP_INDICATION); /* if we were dynamically allocated at accept_cb() time, let's * self-destruct now. A new connection will re-create the ASP. */ if (asp->dyn_allocated) { /* avoid re-entrance via osmo_stream_srv_destroy() which * called us */ asp->server = NULL; osmo_ss7_asp_destroy(asp); } return 0; } /* server has accept()ed a new SCTP association, let's find the ASP for * it (if any) */ static int xua_accept_cb(struct osmo_stream_srv_link *link, int fd) { struct osmo_xua_server *oxs = osmo_stream_srv_link_get_data(link); struct osmo_stream_srv *srv; struct osmo_ss7_asp *asp; char *sock_name = osmo_sock_get_name(link, fd); LOGP(DLSS7, LOGL_INFO, "%s: New %s connection accepted\n", sock_name, get_value_string(osmo_ss7_asp_protocol_vals, oxs->cfg.proto)); if (oxs->cfg.proto == OSMO_SS7_ASP_PROT_IPA) { srv = osmo_stream_srv_create(oxs, link, fd, ipa_srv_conn_cb, xua_srv_conn_closed_cb, NULL); } else { srv = osmo_stream_srv_create(oxs, link, fd, xua_srv_conn_cb, xua_srv_conn_closed_cb, NULL); } if (!srv) { LOGP(DLSS7, LOGL_ERROR, "%s: Unable to create stream server " "for connection\n", sock_name); close(fd); talloc_free(sock_name); return -1; } asp = osmo_ss7_asp_find_by_socket_addr(fd); if (asp) { LOGP(DLSS7, LOGL_INFO, "%s: matched connection to ASP %s\n", sock_name, asp->cfg.name); } else { if (!oxs->cfg.accept_dyn_reg) { LOGP(DLSS7, LOGL_NOTICE, "%s: SCTP connection without matching " "ASP definition and no dynamic registration enabled, terminating\n", sock_name); } else { char namebuf[32]; static uint32_t dyn_asp_num = 0; snprintf(namebuf, sizeof(namebuf), "asp-dyn-%u", dyn_asp_num++); asp = osmo_ss7_asp_find_or_create(oxs->inst, namebuf, 0, 0, oxs->cfg.proto); if (asp) { LOGP(DLSS7, LOGL_INFO, "%s: created dynamicASP %s\n", sock_name, asp->cfg.name); asp->cfg.is_server = true; asp->dyn_allocated = true; asp->server = srv; osmo_ss7_asp_restart(asp); } } if (!asp) { osmo_stream_srv_destroy(srv); talloc_free(sock_name); return -1; } } /* update the ASP reference back to the server over which the * connection came in */ asp->server = srv; asp->xua_server = oxs; llist_add_tail(&asp->siblings, &oxs->asp_list); /* update the ASP socket name */ if (asp->sock_name) talloc_free(asp->sock_name); asp->sock_name = talloc_reparent(link, asp, sock_name); /* make sure the conn_cb() is called with the asp as private * data */ osmo_stream_srv_set_data(srv, asp); /* send M-SCTP_ESTABLISH.ind to Layer Manager */ osmo_fsm_inst_dispatch(asp->fi, XUA_ASP_E_SCTP_EST_IND, 0); xua_asp_send_xlm_prim_simple(asp, OSMO_XLM_PRIM_M_SCTP_ESTABLISH, PRIM_OP_INDICATION); return 0; } /*! \brief send a fully encoded msgb via a given ASP * \param[in] asp Application Server Process through which to send * \param[in] msg message buffer to transmit. Ownership transferred. * \returns 0 on success; negative in case of error */ int osmo_ss7_asp_send(struct osmo_ss7_asp *asp, struct msgb *msg) { OSMO_ASSERT(ss7_initialized); switch (asp->cfg.proto) { case OSMO_SS7_ASP_PROT_SUA: msgb_sctp_ppid(msg) = SUA_PPID; break; case OSMO_SS7_ASP_PROT_M3UA: msgb_sctp_ppid(msg) = M3UA_PPID; break; case OSMO_SS7_ASP_PROT_IPA: break; default: OSMO_ASSERT(0); } if (asp->cfg.is_server) { if (!asp->server) { LOGPASP(asp, DLSS7, LOGL_ERROR, "Cannot transmit, no asp->server\n"); /* FIXME: what to do here? delete the route? send DUNA? */ msgb_free(msg); return -EIO; } osmo_stream_srv_send(asp->server, msg); } else { if (!asp->client) { LOGPASP(asp, DLSS7, LOGL_ERROR, "Cannot transmit, no asp->client\n"); /* FIXME: what to do here? delete the route? send DUNA? */ msgb_free(msg); return -EIO; } osmo_stream_cli_send(asp->client, msg); } return 0; } void osmo_ss7_asp_disconnect(struct osmo_ss7_asp *asp) { if (asp->server) osmo_stream_srv_destroy(asp->server); /* the close_cb() will handle the remaining cleanup here */ else if (asp->client) xua_cli_close_and_reconnect(asp->client); } /*********************************************************************** * SS7 xUA Server ***********************************************************************/ struct osmo_xua_server * osmo_ss7_xua_server_find(struct osmo_ss7_instance *inst, enum osmo_ss7_asp_protocol proto, uint16_t local_port) { struct osmo_xua_server *xs; OSMO_ASSERT(ss7_initialized); llist_for_each_entry(xs, &inst->xua_servers, list) { if (proto == xs->cfg.proto && local_port == xs->cfg.local.port) return xs; } return NULL; } /*! \brief create a new xUA server listening to given ip/port * \param[in] ctx talloc allocation context * \param[in] proto protocol (xUA variant) to use * \param[in] local_port local SCTP port to bind/listen to * \param[in] local_host local IP address to bind/listen to (optional) * \returns callee-allocated \ref osmo_xua_server in case of success */ struct osmo_xua_server * osmo_ss7_xua_server_create(struct osmo_ss7_instance *inst, enum osmo_ss7_asp_protocol proto, uint16_t local_port, const char *local_host) { struct osmo_xua_server *oxs = talloc_zero(inst, struct osmo_xua_server); int rc; OSMO_ASSERT(ss7_initialized); if (!oxs) return NULL; LOGP(DLSS7, LOGL_INFO, "Creating %s Server %s:%u\n", get_value_string(osmo_ss7_asp_protocol_vals, proto), local_host, local_port); INIT_LLIST_HEAD(&oxs->asp_list); oxs->cfg.proto = proto; oxs->cfg.local.port = local_port; oxs->cfg.local.host = talloc_strdup(oxs, local_host); oxs->server = osmo_stream_srv_link_create(oxs); osmo_stream_srv_link_set_data(oxs->server, oxs); osmo_stream_srv_link_set_accept_cb(oxs->server, xua_accept_cb); osmo_stream_srv_link_set_nodelay(oxs->server, true); osmo_stream_srv_link_set_addr(oxs->server, oxs->cfg.local.host); osmo_stream_srv_link_set_port(oxs->server, oxs->cfg.local.port); osmo_stream_srv_link_set_proto(oxs->server, asp_proto_to_ip_proto(proto)); rc = osmo_stream_srv_link_open(oxs->server); if (rc < 0) { osmo_stream_srv_link_destroy(oxs->server); oxs->server = NULL; talloc_free(oxs); } oxs->inst = inst; llist_add_tail(&oxs->list, &inst->xua_servers); /* The SUA code internally needs SCCP to work */ if (proto == OSMO_SS7_ASP_PROT_SUA && !inst->sccp) inst->sccp = osmo_sccp_instance_create(inst, NULL); return oxs; } int osmo_ss7_xua_server_set_local_host(struct osmo_xua_server *xs, const char *local_host) { OSMO_ASSERT(ss7_initialized); osmo_talloc_replace_string(xs, &xs->cfg.local.host, local_host); osmo_stream_srv_link_set_addr(xs->server, xs->cfg.local.host); return 0; } void osmo_ss7_xua_server_destroy(struct osmo_xua_server *xs) { struct osmo_ss7_asp *asp, *asp2; if (xs->server) { osmo_stream_srv_link_close(xs->server); osmo_stream_srv_link_destroy(xs->server); } /* iterate and close all connections established in relation * with this server */ llist_for_each_entry_safe(asp, asp2, &xs->asp_list, siblings) osmo_ss7_asp_destroy(asp); llist_del(&xs->list); talloc_free(xs); } bool osmo_ss7_pc_is_local(struct osmo_ss7_instance *inst, uint32_t pc) { OSMO_ASSERT(ss7_initialized); if (osmo_ss7_pc_is_valid(inst->cfg.primary_pc) && pc == inst->cfg.primary_pc) return true; /* FIXME: Secondary and Capability Point Codes */ return false; } 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); osmo_fsm_register(&ipa_asp_fsm); osmo_fsm_register(&xua_default_lm_fsm); ss7_initialized = true; return 0; } int osmo_ss7_tmode_to_xua(enum osmo_ss7_as_traffic_mode tmod) { switch (tmod) { case OSMO_SS7_AS_TMOD_OVERRIDE: return M3UA_TMOD_OVERRIDE; case OSMO_SS7_AS_TMOD_LOADSHARE: return M3UA_TMOD_LOADSHARE; case OSMO_SS7_AS_TMOD_BCAST: return M3UA_TMOD_BCAST; default: return -1; } } enum osmo_ss7_as_traffic_mode osmo_ss7_tmode_from_xua(uint32_t in) { switch (in) { case M3UA_TMOD_OVERRIDE: default: return OSMO_SS7_AS_TMOD_OVERRIDE; case M3UA_TMOD_LOADSHARE: return OSMO_SS7_AS_TMOD_LOADSHARE; case M3UA_TMOD_BCAST: return OSMO_SS7_AS_TMOD_BCAST; } }