/* TCAP ID based ASP Load-Sharing */ /* (C) 2025 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 #include #include #include #include #include #include "ss7_as.h" #include "sccp_internal.h" #include "ss7_asp.h" #include "ss7_internal.h" #include "tcap_as_loadshare.h" #include "tcap_trans_tracking.h" #include "xua_internal.h" #define OTID_SET 1 << 0 #define DTID_SET 1 << 1 struct tcap_parsed { TCAP_TCMessage_PR present; uint32_t otid; uint32_t dtid; }; static inline uint32_t tcap_id_from_octet_str(const OCTET_STRING_t *src) { OSMO_ASSERT(src->size == 4); return osmo_load32be(src->buf); } /* returns negative on error, mask with any/both OTID_SET|DTID_SET on success */ static int parse_tcap(struct osmo_ss7_as *as, const uint8_t *data, size_t len, struct tcap_parsed *ids) { int rc = 0; asn_dec_rval_t asn_rc; struct TCAP_TCMessage tcap = { 0 }; struct TCAP_TCMessage *tcapmsg = &tcap; OSMO_ASSERT(ids); asn_rc = ber_decode(0, &asn_DEF_TCAP_TCMessage, (void **)&tcapmsg, data, len); if (asn_rc.code != RC_OK) { LOGPAS(as, DLTCAP, LOGL_DEBUG, "Error decoding TCAP message rc: %d, message: %s\n", asn_rc.code, osmo_hexdump(data, len)); rc = -EINVAL; goto free_asn; } ids->present = tcapmsg->present; switch (tcapmsg->present) { case TCAP_TCMessage_PR_begin: { TCAP_Begin_t part = tcapmsg->choice.begin; ids->otid = tcap_id_from_octet_str(&part.otid); rc = OTID_SET; break; } case TCAP_TCMessage_PR_continue: { TCAP_Continue_t part = tcapmsg->choice.Continue; ids->otid = tcap_id_from_octet_str(&part.otid); ids->dtid = tcap_id_from_octet_str(&part.dtid); rc = OTID_SET | DTID_SET; break; } case TCAP_TCMessage_PR_end: { TCAP_End_t part = tcapmsg->choice.end; ids->dtid = tcap_id_from_octet_str(&part.dtid); rc = DTID_SET; break; } case TCAP_TCMessage_PR_abort: { TCAP_Abort_t part = tcapmsg->choice.abort; ids->dtid = tcap_id_from_octet_str(&part.dtid); rc = DTID_SET; break; } /* No TID present */ case TCAP_TCMessage_PR_unidirectional: rc = 0; break; default: rc = -EINVAL; break; } /* Only asn_fprint is available, but no asn_sprint: * asn_fprint(stdout, &asn_DEF_TCAP_TCMessage, tcapmsg); */ free_asn: ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_TCAP_TCMessage, tcapmsg); return rc; } static inline uint32_t tcap_gen_hash(uint32_t pc, uint8_t ssn) { ssn ^= ((pc >> 24) & 0xff); return ((uint32_t)ssn << 24) | (pc & 0xffffff); } static inline uint64_t tcap_gen_hash_addr(const struct osmo_sccp_addr *addr) { uint8_t ssn = 0; uint32_t pc = 0xffffffff; if (addr->presence & OSMO_SCCP_ADDR_T_PC) pc = addr->pc; if (addr->presence & OSMO_SCCP_ADDR_T_SSN) ssn = addr->ssn; return tcap_gen_hash(pc, ssn); } /* TODO: potential optimisation: * Use a sorted list under the hash (tid_range{hash(pc, ssn, tcrng_entry)} -> (struct tcrng_entry {hlist, tid_start, tid_end}) */ static struct osmo_ss7_asp *tcap_hlist_get(const struct osmo_ss7_as *as, uint32_t pc, uint8_t ssn, uint32_t tid) { struct tcap_range *tcrng; struct osmo_ss7_asp *asp = NULL; hash_for_each_possible(as->tcap.tid_ranges, tcrng, list, tcap_gen_hash(pc, ssn)) { if (tcrng->pc != pc || tcrng->ssn != ssn) continue; if (tcap_range_matches(tcrng, tid)) { asp = tcrng->asp; break; } } return asp; } struct osmo_ss7_asp *tcap_as_asp_find_by_tcap_id( struct osmo_ss7_as *as, struct osmo_sccp_addr *calling_addr, struct osmo_sccp_addr *called_addr, uint32_t otid) { struct osmo_ss7_asp *asp = NULL; uint8_t ssn = 0; uint32_t pc = TCAP_PC_WILDCARD; if (called_addr->presence & OSMO_SCCP_ADDR_T_PC) pc = called_addr->pc; if (called_addr->presence & OSMO_SCCP_ADDR_T_SSN) ssn = called_addr->ssn; /* check full range of PC/SSN */ if (as->tcap.contains_pc && as->tcap.contains_ssn) { asp = tcap_hlist_get(as, pc, ssn, otid); if (asp) return asp; } /* check with PC wildcard */ if (as->tcap.contains_ssn) { asp = tcap_hlist_get(as, TCAP_PC_WILDCARD, ssn, otid); if (asp) return asp; } /* check with SSN wildcard */ if (as->tcap.contains_pc) { asp = tcap_hlist_get(as, pc, TCAP_SSN_WILDCARD, otid); if (asp) return asp; } /* check with PC/SSN wildcard */ return tcap_hlist_get(as, TCAP_PC_WILDCARD, TCAP_SSN_WILDCARD, otid); } static struct tcap_range *tcap_overlap_tid(struct osmo_ss7_as *as, uint32_t pc, uint8_t ssn, uint32_t tid_start, uint32_t tid_end) { struct tcap_range *tcrng; hash_for_each_possible(as->tcap.tid_ranges, tcrng, list, tcap_gen_hash(pc, ssn)) { if (tcrng->pc != pc || tcrng->ssn != ssn) continue; if (tcap_range_overlaps(tcrng, tid_start, tid_end)) return tcrng; } return NULL; } static struct osmo_ss7_asp *find_asp_no_tcap_range(struct osmo_ss7_as *as) { struct osmo_ss7_asp *asp = NULL; for (int i = 0; i < ARRAY_SIZE(as->cfg.asps); i++) { asp = as->cfg.asps[i]; if (!asp) continue; if (!asp->tcap.enabled) return asp; } return asp; } static bool ssn_contains_tcap(uint8_t ssn) { switch (ssn) { case OSMO_SCCP_SSN_MAP: case OSMO_SCCP_SSN_HLR: case OSMO_SCCP_SSN_VLR: case OSMO_SCCP_SSN_MSC: case OSMO_SCCP_SSN_EIR: case OSMO_SCCP_SSN_AUC: case OSMO_SCCP_SSN_TC_TEST: case OSMO_SCCP_SSN_GMLC_MAP: case OSMO_SCCP_SSN_CAP: case OSMO_SCCP_SSN_gsmSCF_MAP: case OSMO_SCCP_SSN_SIWF_MAP: case OSMO_SCCP_SSN_SGSN_MAP: case OSMO_SCCP_SSN_GGSN_MAP: /* SSNs known to use TCAP */ return true; default: return false; } } /** Traffic from the TCAP ASP -> AS -> osmo-stp, only used to update transaction tracking * * @param as * @param asp asp sent the \ref sccp_msg message towards osmo-stp * @param opc M3UA opc * @param dpc M3UA DPC * @param sccp_msg pointer to a msg. * @return 0 on successful handling, < 0 on error cases (missing IE, decoding errors) */ int tcap_as_rx_sccp_asp(struct osmo_ss7_as *as, struct osmo_ss7_asp *asp, uint32_t opc, uint32_t dpc, struct msgb *sccp_msg) { struct tcap_parsed parsed = {}; struct xua_msg_part *ie_data; struct osmo_sccp_addr calling_addr, called_addr; int rc; struct xua_msg *sua = osmo_sccp_to_xua(sccp_msg); if (!sua) { LOGPAS(as, DLTCAP, LOGL_ERROR, "Unable to parse SCCP message\n"); return -1; } /* TCAP uses only connectionless SCCP messages */ if (sua->hdr.msg_class != SUA_MSGC_CL && sua->hdr.msg_class != SUA_CL_CLDT) return -2; rc = sua_addr_parse(&calling_addr, sua, SUA_IEI_SRC_ADDR); if (rc < 0) { LOGPAS(as, DLTCAP, LOGL_ERROR, "Unable to parse SCCP Destination Address\n"); return -3; } /* retrieve + decode destination address */ rc = sua_addr_parse(&called_addr, sua, SUA_IEI_DEST_ADDR); if (rc < 0) { LOGPAS(as, DLTCAP, LOGL_ERROR, "Unable to parse SCCP Destination Address\n"); return -4; } if (!ssn_contains_tcap(called_addr.ssn)) { /* No TCAP */ return 0; } /* TCAP transaction tracking requires point codes */ if (!(calling_addr.presence & OSMO_SCCP_ADDR_T_PC)) { /* use M3UA OPC instead */ calling_addr.pc = opc; calling_addr.presence |= OSMO_SCCP_ADDR_T_PC; } if (!(called_addr.presence & OSMO_SCCP_ADDR_T_PC)) { /* use M3UA DPC instead */ called_addr.pc = dpc; called_addr.presence |= OSMO_SCCP_ADDR_T_PC; } /* retrieve the SCCP payload (actual encoded TCAP data) */ ie_data = xua_msg_find_tag(sua, SUA_IEI_DATA); if (!ie_data) return -6; rc = parse_tcap(as, ie_data->dat, ie_data->len, &parsed); if (rc <= 0) { LOGPAS(as, DLTCAP, LOGL_ERROR, "Failed get TCAP otid/dtid.\n"); return -7; } LOGPAS(as, DLTCAP, LOGL_INFO, "Looking up transaction for type 0x%02x, otid=%u dtid=%u\n", parsed.present, parsed.otid, parsed.dtid); /* TCAP messages towards the IPAC nodes */ switch (parsed.present) { case TCAP_TCMessage_PR_begin: if (!(rc & OTID_SET)) { /* FIXME: failure case */ return -8; } tcap_trans_track_entry_create(as, asp, &calling_addr, &parsed.otid, &called_addr, NULL); break; case TCAP_TCMessage_PR_continue: if (!((rc & OTID_SET) && (rc & DTID_SET))) { /* FIXME: failure case */ return -8; } /* only hit/update the transaction tracking */ tcap_trans_track_continue(as, &calling_addr, &parsed.otid, &called_addr, &parsed.dtid); break; case TCAP_TCMessage_PR_abort: case TCAP_TCMessage_PR_end: if (!(rc & DTID_SET)) { /* FIXME: failure case */ return -8; } /* only hit/update the transaction tracking */ tcap_trans_track_end(as, &calling_addr, NULL, &called_addr, &parsed.dtid); break; case TCAP_TCMessage_PR_unidirectional: case TCAP_TCMessage_PR_NOTHING: default: /* TODO: what to do with those messages? */ return -9; } return 0; } /** Send UDTS to indicate that the originating UDT could not be delivered to its destination * @param as * @param orig_mtp MTP routing information of the originating message (message that could not be delivered) * @param orig_sua Originating message that could not be delivered * @param cause_code the return cause of the UDTS * @return 0 on success, negative on error */ static int send_back_udts(struct osmo_ss7_as *as, const struct osmo_mtp_transfer_param *orig_mtp, const struct xua_msg *orig_sua, uint8_t cause_code) { struct msgb *msg; struct xua_msg *sua; int rc = -EINVAL; uint32_t spare_proto = 0; struct osmo_mtp_transfer_param new_mtp; uint32_t rctx; OSMO_ASSERT(orig_sua->hdr.msg_class == SUA_MSGC_CL && orig_sua->hdr.msg_type == SUA_CL_CLDT); if (!xua_msg_get_u32p(orig_sua, SUA_IEI_PROTO_CLASS, &spare_proto)) return -EINVAL; /* Check if Return on Error is set */ if ((spare_proto & 0xf0) != 0x80) return 0; struct xua_msg_part *rctx_ie = xua_msg_find_tag(orig_sua, SUA_IEI_ROUTE_CTX); if (rctx_ie) rctx = xua_msg_part_get_u32(rctx_ie); else rctx = 0; /* Routing Context should be there as per proto... */ sua = sua_gen_cldr(orig_sua, rctx, cause_code); if (!sua) return -ENOMEM; LOGPAS(as, DLTCAP, LOGL_INFO, "Tx UDTS: %s\n", xua_msg_dump(sua, &xua_dialect_sua)); msg = osmo_sua_to_sccp(sua); if (!msg) { rc = -ENOMEM; goto free_sua; } new_mtp = *orig_mtp; new_mtp.opc = orig_mtp->dpc; new_mtp.dpc = orig_mtp->opc; hmrt_mtp_xfer_request_l4_to_l3(as->inst, &new_mtp, msgb_data(msg), msgb_length(msg)); msgb_free(msg); free_sua: xua_msg_free(sua); return rc; } /*! Traffic STP -> AS -> ASP (Tx path) Loadshare towards the TCAP routing AS * * \param[out] rasp the selected ASP if any, can be NULL * \param[in] as * \param[in] opc the OPC from MTP * \param[in] dpc the DPC from MTP * \param[in] mtp MTP routing information * \param[in] sccp_msg the SCCP message. Callee takes ownership. * \return 0: on succcess (msg handled by the callee), * -EPROTONOSUPPORT: let caller (regular loadsharing) handle those. */ static int asp_loadshare_tcap_sccp(struct osmo_ss7_asp **rasp, struct osmo_ss7_as *as, const struct osmo_mtp_transfer_param *mtp, struct msgb *sccp_msg) { struct tcap_parsed parsed = {}; struct xua_msg *sua; struct xua_msg_part *ie_data; struct osmo_sccp_addr calling_addr, called_addr; struct osmo_ss7_asp *asp = NULL; int rc; OSMO_ASSERT(rasp); /* decode SCCP and convert to a SUA/xUA representation */ sua = osmo_sccp_to_xua(sccp_msg); if (!sua) { LOGPAS(as, DLTCAP, LOGL_ERROR, "Unable to parse SCCP message\n"); rc = -EPROTONOSUPPORT; goto out_free_msgb; } /* TCAP uses only connectionless SCCP messages */ if (sua->hdr.msg_class != SUA_MSGC_CL && sua->hdr.msg_class != SUA_CL_CLDT) { /* ignoring packets */ rc = -EPROTONOSUPPORT; goto out_free_sua; } rc = sua_addr_parse(&calling_addr, sua, SUA_IEI_SRC_ADDR); if (rc < 0) { LOGPAS(as, DLTCAP, LOGL_ERROR, "Unable to parse SCCP Source Address\n"); goto out_free_sua; } rc = sua_addr_parse(&called_addr, sua, SUA_IEI_DEST_ADDR); if (rc < 0) { LOGPAS(as, DLTCAP, LOGL_ERROR, "Unable to parse SCCP Destination Address\n"); goto out_free_sua; } if (!(called_addr.presence & OSMO_SCCP_ADDR_T_SSN) || !ssn_contains_tcap(called_addr.ssn)) { /* No tcap, return NULL */ rc = -EPROTONOSUPPORT; goto out_free_sua; } /* TCAP transaction tracking requires point codes */ if (!(calling_addr.presence & OSMO_SCCP_ADDR_T_PC)) { /* use M3UA OPC instead */ calling_addr.pc = mtp->opc; calling_addr.presence |= OSMO_SCCP_ADDR_T_PC; } if (!(called_addr.presence & OSMO_SCCP_ADDR_T_PC)) { /* use M3UA DPC instead */ called_addr.pc = mtp->dpc; called_addr.presence |= OSMO_SCCP_ADDR_T_PC; } /* retrieve the SCCP payload (TCAP data) */ ie_data = xua_msg_find_tag(sua, SUA_IEI_DATA); if (!ie_data) { rc = -ENODATA; goto out_free_sua; } rc = parse_tcap(as, ie_data->dat, ie_data->len, &parsed); LOGPAS(as, DLTCAP, LOGL_DEBUG, "TCAP decoded rc=%d otid=%u dtid=%u\n", rc, parsed.otid, parsed.dtid); if (rc <= 0) { rate_ctr_inc2(as->ctrg, SS7_AS_CTR_RX_TCAP_FAILED); LOGPAS(as, DLTCAP, LOGL_ERROR, "Failed get TCAP otid/dtid.\n"); rc = -EINVAL; goto out_free_sua; } rate_ctr_inc2(as->ctrg, SS7_AS_CTR_RX_TCAP_DECODED); /* TCAP messages towards the IPA nodes */ switch (parsed.present) { case TCAP_TCMessage_PR_begin: if (!(rc & OTID_SET)) { rc = -EINVAL; goto out_free_sua; } /* lookup a new ASP */ asp = tcap_as_asp_find_by_tcap_id(as, &calling_addr, &called_addr, parsed.otid); if (asp) { rate_ctr_inc2(as->ctrg, SS7_AS_CTR_TCAP_ASP_SELECTED); } else { /* if no ASP found for this TCAP, try to find a non-tcap-range ASP as fallback*/ asp = find_asp_no_tcap_range(as); if (asp) rate_ctr_inc2(as->ctrg, SS7_AS_CTR_TCAP_ASP_FALLBACK); else { /* couldn't find a suitable canditate for OTID */ rate_ctr_inc2(as->ctrg, SS7_AS_CTR_TCAP_ASP_FAILED); LOGPAS(as, DLTCAP, LOGL_DEBUG, "Couldn't find a suitable canditate for otid %u\n", parsed.otid); rc = -ENOKEY; goto out_free_sua; } } tcap_trans_track_begin(as, asp, &called_addr, NULL, &calling_addr, &parsed.otid); rc = 0; break; case TCAP_TCMessage_PR_continue: if (!((rc & OTID_SET) && (rc & DTID_SET))) { rc = -EINVAL; goto out_free_sua; } asp = tcap_trans_track_continue(as, &called_addr, &parsed.dtid, &calling_addr, &parsed.otid); rc = asp ? 0 : -ENOKEY; break; case TCAP_TCMessage_PR_abort: case TCAP_TCMessage_PR_end: if (!(rc & DTID_SET)) { /* FIXME: failure case */ rc = -EINVAL; goto out_free_sua; } asp = tcap_trans_track_end(as, &called_addr, &parsed.dtid, &calling_addr, NULL); rc = asp ? 0 : -ENOKEY; break; case TCAP_TCMessage_PR_unidirectional: case TCAP_TCMessage_PR_NOTHING: default: /* Ignore, let regular loadsharing handle those */ rc = -EPROTONOSUPPORT; break; } out_free_sua: /* RFC3868 4.7.3: "If an ASP is not available, the SG may generate (X)UDTS "routing failure", * if the return option is used." * See also ITU Q.714 4.2 */ if (rc < 0 && rc != -EPROTONOSUPPORT) { send_back_udts(as, mtp, sua, SCCP_RETURN_CAUSE_SUBSYSTEM_FAILURE); rc = 0; } xua_msg_free(sua); out_free_msgb: msgb_free(sccp_msg); *rasp = asp; return rc; } /*! Entrypoint for M3UA messages towards the TCAP nodes * * @param[out] asp Result pointer of the selected asp. Set to NULL if return code is != 0 * @param[in] as * @param[in] xua * @return 0: on succcess (msg handled by the callee), * -EPROTONOSUPPORT: let caller (regular loadsharing) handle those. */ /* return 0 and asp is set */ int tcap_as_select_asp_loadshare(struct osmo_ss7_asp **asp, struct osmo_ss7_as *as, const struct xua_msg *xua) { uint8_t service_ind = xua->mtp.sio & 0xF; struct xua_msg_part *m3ua_data_ie; struct msgb *sccp_msg; uint8_t *cur; OSMO_ASSERT(asp); *asp = NULL; if (service_ind != MTP_SI_SCCP) return -EPROTONOSUPPORT; /* we only care about actual M3UA data transfer messages */ if (xua->hdr.msg_class != M3UA_MSGC_XFER || xua->hdr.msg_type != M3UA_XFER_DATA) return -EPROTONOSUPPORT; /* we only care about SCCP as higher layer protocol. * extract the SCCP payload and convert to a msgb */ m3ua_data_ie = xua_msg_find_tag(xua, M3UA_IEI_PROT_DATA); if (!m3ua_data_ie) { LOGPAS(as, DLTCAP, LOGL_ERROR, "Couldn't find M3UA protocol data\n"); return -EPROTONOSUPPORT; } sccp_msg = msgb_alloc(m3ua_data_ie->len, "loadshare_tcap"); if (!sccp_msg) { LOGPAS(as, DLTCAP, LOGL_ERROR, "Unable to allocate SCCP message buffer\n"); return -ENOMEM; } cur = msgb_put(sccp_msg, m3ua_data_ie->len); memcpy(cur, m3ua_data_ie->dat, m3ua_data_ie->len); sccp_msg->l2h = cur + sizeof(struct m3ua_data_hdr); return asp_loadshare_tcap_sccp(asp, as, &xua->mtp, sccp_msg); } enum ipa_tcap_routing_msg_types { MT_TID_ADD_RANGE = 0x01, MT_TID_ACK = 0x02, MT_TID_NACK = 0x03, }; enum ipa_tcap_routing_nack_error { NACK_ERR_SYS_FAILURE = 0x01, /* system failure */ NACK_ERR_EALREADY = 0x72, /* already in use */ }; struct ipa_tcap_routing_hdr { uint8_t mt; uint32_t seq; uint8_t data[0]; } __attribute__((packed)); struct ipa_tcap_routing_add_range { uint32_t tid_start; uint32_t tid_end; uint32_t pc; uint8_t ssn; } __attribute__((packed)); struct ipa_tcap_routing_nack { uint8_t err; } __attribute__((packed)); static struct msgb *ipa_tcap_routing_alloc(uint32_t seq_nr, uint8_t mt) { struct ipa_tcap_routing_hdr hdr = { .mt = mt, .seq = osmo_htonl(seq_nr), }; struct msgb *msg = osmo_ipa_msg_alloc(16); if (!msg) return NULL; void *dst = msgb_put(msg, sizeof(hdr)); memcpy(dst, &hdr, sizeof(hdr)); return msg; } static int ipa_tx_tcap_routing_ack(struct osmo_ss7_asp *asp, uint32_t seq_nr) { struct msgb *msg = ipa_tcap_routing_alloc(seq_nr, MT_TID_ACK); if (!msg) return -ENOMEM; osmo_ipa_msg_push_headers(msg, IPAC_PROTO_OSMO, IPAC_PROTO_EXT_TCAP_ROUTING); return osmo_ss7_asp_send(asp, msg); } static int ipa_tx_tcap_routing_nack(struct osmo_ss7_asp *asp, uint32_t seq_nr, uint8_t err_code) { struct msgb *msg = ipa_tcap_routing_alloc(seq_nr, MT_TID_NACK); if (!msg) return -ENOMEM; msgb_put_u8(msg, err_code); osmo_ipa_msg_push_headers(msg, IPAC_PROTO_OSMO, IPAC_PROTO_EXT_TCAP_ROUTING); return osmo_ss7_asp_send(asp, msg); } /** Entrypoint for IPA TCAP Routing messages, parses and handles those * * @param asp * @param msg the message buffer. Callee takes ownership! * @return 0 on success */ int ipa_rx_msg_osmo_ext_tcap_routing(struct osmo_ss7_asp *asp, struct msgb *msg) { int rc = 0; struct osmo_ss7_as *as = ipa_find_as_for_asp(asp); struct ipa_tcap_routing_hdr *hdr; enum ipa_tcap_routing_msg_types routing_msg; if (!as) { LOGPASP(asp, DLTCAP, LOGL_ERROR, "Rx message for IPA ASP without AS?!\n"); rc = -ENOENT; goto out; } /* pull the IPA and OSMO_EXT header */ hdr = (struct ipa_tcap_routing_hdr *) msgb_data(msg); if (msgb_length(msg) < sizeof(struct ipa_tcap_routing_hdr)) { LOGPASP(asp, DLTCAP, LOGL_ERROR, "TCAP routing message too short\n"); rc = -EINVAL; goto out; } routing_msg = (enum ipa_tcap_routing_msg_types) hdr->mt; switch (routing_msg) { case MT_TID_ADD_RANGE: { struct tcap_range *tcrng; struct ipa_tcap_routing_add_range tcar = {}; if (!as->cfg.loadshare.tcap.enabled || as->cfg.mode != OSMO_SS7_AS_TMOD_LOADSHARE) LOGPASP(asp, DLTCAP, LOGL_NOTICE, "Wrong traffic mode %s on AS %s will not use TCAP Ranges\n", osmo_ss7_as_traffic_mode_name(as->cfg.mode), as->cfg.name); if (msgb_length(msg) < sizeof(*hdr) + sizeof(struct ipa_tcap_routing_add_range)) { LOGPASP(asp, DLTCAP, LOGL_ERROR, "TCAP routing message is too small\n"); rc = -EINVAL; goto out; } msgb_pull(msg, sizeof(*hdr)); tcar.tid_start = msgb_pull_u32(msg); tcar.tid_end = msgb_pull_u32(msg); tcar.pc = msgb_pull_u32(msg); tcar.ssn = msgb_pull_u8(msg); LOGPASP(asp, DLTCAP, LOGL_INFO, "Rx: TCAP Add Range command: seq: %u pc: %u ssn: %u [%u-%u]\n", osmo_ntohl(hdr->seq), tcar.pc, tcar.ssn, tcar.tid_start, tcar.tid_end); tcrng = tcap_overlap_tid(as, tcar.pc, tcar.ssn, tcar.tid_start, tcar.tid_end); if (tcrng) { LOGPASP(asp, DLTCAP, LOGL_ERROR, "New TCAP Range overlaps with existing range to ASP %s [%u-%u]. Rejecting Add Range Command seq: %u pc: %u ssn: %u [%u-%u]\n", tcrng->asp->cfg.name, tcrng->tid_start, tcrng->tid_end, osmo_ntohl(hdr->seq), tcar.pc, tcar.ssn, tcar.tid_start, tcar.tid_end); rc = ipa_tx_tcap_routing_nack(asp, osmo_ntohl(hdr->seq), NACK_ERR_EALREADY); goto out; } tcrng = tcap_range_alloc(as, asp, tcar.tid_start, tcar.tid_end, tcar.pc, tcar.ssn); if (!tcrng) { LOGPASP(asp, DLTCAP, LOGL_ERROR, "TCAP Add Range: failed to allocate memory\n"); rc = ipa_tx_tcap_routing_nack(asp, osmo_ntohl(hdr->seq), NACK_ERR_SYS_FAILURE); goto out; } if (tcar.pc != TCAP_PC_WILDCARD) as->tcap.contains_pc = true; if (tcar.ssn != TCAP_SSN_WILDCARD) as->tcap.contains_ssn = true; asp->tcap.enabled = true; rc = ipa_tx_tcap_routing_ack(asp, osmo_ntohl(hdr->seq)); break; } case MT_TID_ACK: /* shouldn't received from other end */ case MT_TID_NACK: /* shouldn't received from other end */ default: rc = -EINVAL; break; } out: return rc; } /* update the short cuts contains_pc & contains_ssn */ static void tcap_range_as_update_pc_ssn(struct osmo_ss7_as *as) { int i; struct tcap_range *tcrng; struct hlist_node *tmp; bool check_pc = as->tcap.contains_pc; bool found_pc = false; bool check_ssn = as->tcap.contains_ssn; bool found_ssn = false; hash_for_each_safe(as->tcap.tid_ranges, i, tmp, tcrng, list) { if (!check_pc && !check_ssn) break; if (check_pc && tcrng->pc != TCAP_PC_WILDCARD) { check_pc = false; found_pc = true; } if (check_ssn && tcrng->ssn != TCAP_SSN_WILDCARD) { check_ssn = false; found_ssn = true; } } if (as->tcap.contains_pc) as->tcap.contains_pc = found_pc; if (as->tcap.contains_ssn) as->tcap.contains_ssn = found_ssn; } /** Create and alloc a new TCAP range entry * * @param[in] as * @param[in] asp * @param[in] tid_start * @param[in] tid_end * @param[in] pc * @param[in] ssn * @return the TCAP range entry or NULL */ struct tcap_range *tcap_range_alloc(struct osmo_ss7_as *as, struct osmo_ss7_asp *asp, uint32_t tid_start, uint32_t tid_end, uint32_t pc, uint8_t ssn) { struct tcap_range *tcrng = talloc_zero(asp, struct tcap_range); if (!tcrng) return NULL; tcrng->asp = asp; tcrng->pc = pc; tcrng->ssn = ssn; tcrng->tid_start = tid_start; tcrng->tid_end = tid_end; hash_add(as->tcap.tid_ranges, &tcrng->list, tcap_gen_hash(pc, ssn)); return tcrng; } /** Remove and free a single TCAP range entry * * @param[in] tcrng */ void tcap_range_free(struct tcap_range *tcrng) { hash_del(&tcrng->list); talloc_free(tcrng); } /** Checks if a tid matches to a specific range * * @param tcrng * @param tid * @return true if tid is included in the range */ bool tcap_range_matches(const struct tcap_range *tcrng, uint32_t tid) { return (tid >= tcrng->tid_start) && (tid <= tcrng->tid_end); } /** Checks if a tid rnage overlaps with another range * * @param a * @param tid_start * @param tid_end * @return */ bool tcap_range_overlaps(const struct tcap_range *a, uint32_t tid_start, uint32_t tid_end) { struct tcap_range b = { .tid_start = tid_start, .tid_end = tid_end }; return tcap_range_matches(&b, a->tid_start) || tcap_range_matches(&b, a->tid_end) || tcap_range_matches(a, tid_start) || tcap_range_matches(a, tid_end); } static void _tcap_range_asp_down(struct osmo_ss7_as *as, struct osmo_ss7_asp *asp) { int i; struct tcap_range *tcrng; struct hlist_node *tmp; tcap_trans_track_entries_free_by_asp(as, asp); hash_for_each_safe(as->tcap.tid_ranges, i, tmp, tcrng, list) { if (tcrng->asp == asp) tcap_range_free(tcrng); } } void tcap_as_del_asp(struct osmo_ss7_as *as, struct osmo_ss7_asp *asp) { if (!asp->tcap.enabled) return; _tcap_range_asp_down(as, asp); if (as->tcap.contains_pc || as->tcap.contains_ssn) tcap_range_as_update_pc_ssn(as); } void tcap_enable(struct osmo_ss7_as *as) { if (as->cfg.loadshare.tcap.enabled) return; as->cfg.loadshare.tcap.enabled = true; tcap_trans_track_garbage_collect_start(as); } void tcap_disable(struct osmo_ss7_as *as) { if (!as->cfg.loadshare.tcap.enabled) return; as->cfg.loadshare.tcap.enabled = false; as->tcap.contains_pc = false; as->tcap.contains_ssn = false; tcap_trans_track_garbage_collect_stop(as); tcap_trans_track_entries_free_all(as); }