/*********************************************************************** * MTP Level 3 - Signalling message handling (SMH) Figure 23/Q.704 ***********************************************************************/ /* (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 "xua_internal.h" #include "ss7_as.h" #include "ss7_asp.h" #include "ss7_linkset.h" #include "ss7_route.h" #include "ss7_route_table.h" #include "ss7_internal.h" #include "ss7_user.h" /* convert from M3UA message to MTP-TRANSFER.ind osmo_mtp_prim */ struct osmo_mtp_prim *m3ua_to_xfer_ind(struct xua_msg *xua) { struct osmo_mtp_prim *prim; struct osmo_mtp_transfer_param *param; struct xua_msg_part *data_ie = xua_msg_find_tag(xua, M3UA_IEI_PROT_DATA); struct m3ua_data_hdr *data_hdr; struct msgb *upmsg = m3ua_msgb_alloc("M3UA MTP-TRANSFER.ind"); if (!data_ie || data_ie->len < sizeof(*data_hdr)) { /* FIXME: ERROR message */ msgb_free(upmsg); return NULL; } data_hdr = (struct m3ua_data_hdr *) data_ie->dat; /* fill primitive */ prim = (struct osmo_mtp_prim *) msgb_put(upmsg, sizeof(*prim)); param = &prim->u.transfer; osmo_prim_init(&prim->oph, MTP_SAP_USER, OSMO_MTP_PRIM_TRANSFER, PRIM_OP_INDICATION, upmsg); m3ua_dh_to_xfer_param(param, data_hdr); /* copy data */ upmsg->l2h = msgb_put(upmsg, data_ie->len - sizeof(*data_hdr)); memcpy(upmsg->l2h, data_ie->dat+sizeof(*data_hdr), data_ie->len - sizeof(*data_hdr)); return prim; } /* convert from MTP-TRANSFER.req to osmo_mtp_prim */ static struct xua_msg *mtp_prim_to_m3ua(struct osmo_mtp_prim *prim) { struct msgb *msg = prim->oph.msg; struct osmo_mtp_transfer_param *param = &prim->u.transfer; struct m3ua_data_hdr data_hdr; mtp_xfer_param_to_m3ua_dh(&data_hdr, param); return m3ua_xfer_from_data(&data_hdr, msgb_l2(msg), msgb_l2len(msg)); } /* delivery given XUA message to given SS7 user */ static int deliver_to_mtp_user(const struct osmo_ss7_user *osu, struct xua_msg *xua) { struct osmo_mtp_prim *prim; /* Create MTP-TRANSFER.ind and feed to user */ prim = m3ua_to_xfer_ind(xua); if (!prim) return -1; prim->u.transfer = xua->mtp; return osu->prim_cb(&prim->oph, (void *) osu->priv); } /* HMDC -> HMDT: Message for distribution; Figure 25/Q.704 */ /* This means it is a message we received from remote/L2, and it is to * be routed to a local user part */ static int hmdt_message_for_distribution(struct osmo_ss7_instance *inst, struct xua_msg *xua) { struct m3ua_data_hdr *mdh; const struct osmo_ss7_user *osu; uint32_t service_ind; switch (xua->hdr.msg_class) { case M3UA_MSGC_XFER: switch (xua->hdr.msg_type) { case M3UA_XFER_DATA: mdh = data_hdr_from_m3ua(xua); service_ind = mdh->si & 0xf; break; default: LOGP(DLSS7, LOGL_ERROR, "Unknown M3UA XFER Message " "Type %u\n", xua->hdr.msg_type); return -1; } break; case M3UA_MSGC_SNM: /* FIXME */ /* FIXME: SI = Signalling Network Management -> SRM/SLM/STM */ /* FIXME: SI = Signalling Network Testing and Maintenance -> SLTC */ default: /* Discard Message */ LOGP(DLSS7, LOGL_ERROR, "Unknown M3UA Message Class %u\n", xua->hdr.msg_class); return -1; } /* Check for local SSN registered for this DPC/SSN */ osu = inst->user[service_ind]; if (osu) { return deliver_to_mtp_user(osu, xua); } else { LOGP(DLSS7, LOGL_NOTICE, "No MTP-User for SI %u\n", service_ind); /* Discard Message */ /* FIXME: User Part Unavailable HMDT -> HMRT */ return -1; } } /*! Return human readable representation of the route, in a static buffer. * This uses both osmo_ss7_pointcode_print() and osmo_ss7_pointcode_print2(), so pairing * osmo_ss7_route_name() with osmo_ss7_pointcode_print() in the same printf statement is likely to * conflict. * \param[in] rt The route information to print, or NULL. * \param[in] list_asps If true, append info for all ASPs for the route's AS. * \returns A string constant or static buffer. */ const char *osmo_ss7_route_name(struct osmo_ss7_route *rt, bool list_asps) { static char buf[256]; char *pos = buf; struct osmo_ss7_instance *inst; size_t l; if (!rt) return "no route"; inst = rt->rtable->inst; #define APPEND(fmt, args ...) \ do { \ l = snprintf(pos, sizeof(buf) - (pos - buf), fmt, ## args); \ pos += l; \ if (pos - buf >= sizeof(buf) ) \ goto out; \ } while (0) APPEND("pc=%u=%s mask=0x%x=%s", rt->cfg.pc, osmo_ss7_pointcode_print(inst, rt->cfg.pc), rt->cfg.mask, osmo_ss7_pointcode_print2(inst, rt->cfg.mask)); if (rt->dest.as) { struct osmo_ss7_as *as = rt->dest.as; int i; APPEND(" via AS %s proto=%s", as->cfg.name, osmo_ss7_asp_protocol_name(as->cfg.proto)); if (list_asps) { for (i = 0; i < ARRAY_SIZE(as->cfg.asps); i++) { struct osmo_ss7_asp *asp = as->cfg.asps[i]; if (!asp) continue; APPEND(" ASP"); if (asp->cfg.name) APPEND(" %s", asp->cfg.name); if (asp->sock_name) APPEND(" %s", asp->sock_name); } } } else if (rt->dest.linkset) APPEND(" via linkset %s", rt->dest.linkset->cfg.name); else APPEND(" has no route set"); #undef APPEND out: buf[sizeof(buf)-1] = '\0'; return buf; } /* HMDC->HMRT Msg For Routing; Figure 26/Q.704 */ /* local message was receive d from L4, SRM, SLM, STM or SLTC, or * remote message received from L2 and HMDC determined msg for routing */ static int hmrt_message_for_routing(struct osmo_ss7_instance *inst, struct xua_msg *xua) { uint32_t dpc = xua->mtp.dpc; struct osmo_ss7_route *rt; /* find route for DPC */ /* FIXME: unify with gen_mtp_transfer_req_xua() */ rt = osmo_ss7_route_lookup(inst, dpc); if (rt) { /* FIXME: DPC SP restart? */ /* FIXME: DPC Congested? */ /* FIXME: Select link based on SLS */ /* FIXME: Transmit over respective Link */ if (rt->dest.as) { struct osmo_ss7_as *as = rt->dest.as; if (log_check_level(DLSS7, LOGL_DEBUG)) { /* osmo_ss7_route_name() calls osmo_ss7_pointcode_print() and * osmo_ss7_pointcode_print2(), guard against its static buffer being * overwritten. */ const char *rt_name = osmo_ss7_route_name(rt, false); DEBUGP(DLSS7, "Found route for dpc=%u=%s: %s\n", dpc, osmo_ss7_pointcode_print(inst, dpc), rt_name); } if (osmo_ss7_as_down(as)) { LOGP(DLSS7, LOGL_ERROR, "Unable to route HMRT message: the AS %s is down\n", as->cfg.name); return -ENETDOWN; } rate_ctr_inc2(as->ctrg, SS7_AS_CTR_TX_MSU_TOTAL); switch (as->cfg.proto) { case OSMO_SS7_ASP_PROT_M3UA: DEBUGP(DLSS7, "rt->dest.as proto is M3UA for dpc=%u=%s\n", dpc, osmo_ss7_pointcode_print(inst, dpc)); return m3ua_tx_xua_as(as,xua); case OSMO_SS7_ASP_PROT_IPA: return ipa_tx_xua_as(as, xua); default: LOGP(DLSS7, LOGL_ERROR, "MTP message " "for ASP of unknown protocol %u\n", as->cfg.proto); break; } } else if (rt->dest.linkset) { if (log_check_level(DLSS7, LOGL_ERROR)) { /* osmo_ss7_route_name() calls osmo_ss7_pointcode_print() and * osmo_ss7_pointcode_print2(), guard against its static buffer being * overwritten. */ const char *rt_name = osmo_ss7_route_name(rt, false); LOGP(DLSS7, LOGL_ERROR, "Found route for dpc=%u=%s: %s," " but MTP-TRANSFER.req unsupported for linkset.\n", dpc, osmo_ss7_pointcode_print(inst, dpc), rt_name); } } else OSMO_ASSERT(0); } else { LOGP(DLSS7, LOGL_ERROR, "MTP-TRANSFER.req for DPC %u: " "no route!\n", dpc); /* DPC unknown HMRT -> MGMT */ /* Message Received for inaccesible SP HMRT ->RTPC */ /* Discard Message */ } return -1; } /* HMDC: Received Message L2 -> L3; Figure 24/Q.704 */ /* This means a message was received from L2 and we have to decide if it * is for the local stack (HMDT) or for routng (HMRT) */ int m3ua_hmdc_rx_from_l2(struct osmo_ss7_instance *inst, struct xua_msg *xua) { uint32_t dpc = xua->mtp.dpc; if (osmo_ss7_pc_is_local(inst, dpc)) { DEBUGP(DLSS7, "%s(): found dpc=%u=%s as local\n", __func__, dpc, osmo_ss7_pointcode_print(inst, dpc)); return hmdt_message_for_distribution(inst, xua); } else { DEBUGP(DLSS7, "%s(): dpc=%u=%s not local, message is for routing\n", __func__, dpc, osmo_ss7_pointcode_print(inst, dpc)); return hmrt_message_for_routing(inst, xua); } } /* MTP-User requests to send a MTP-TRANSFER.req via the stack */ int osmo_ss7_user_mtp_xfer_req(struct osmo_ss7_instance *inst, struct osmo_mtp_prim *omp) { struct xua_msg *xua; int rc; OSMO_ASSERT(omp->oph.sap == MTP_SAP_USER); switch (OSMO_PRIM_HDR(&omp->oph)) { case OSMO_PRIM(OSMO_MTP_PRIM_TRANSFER, PRIM_OP_REQUEST): xua = mtp_prim_to_m3ua(omp); xua->mtp = omp->u.transfer; /* normally we would call hmrt_message_for_routing() * here, if we were to follow the state diagrams of the * ITU-T Q.70x specifications. However, what if a local * MTP user sends a MTP-TRANSFER.req to a local SSN? * This wouldn't work as per the spec, but I believe it * is a very useful feature (aka "loopback device" in * IPv4). So we call m3ua_hmdc_rx_from_l2() just like * the MTP-TRANSFER had been received from L2. */ rc = m3ua_hmdc_rx_from_l2(inst, xua); xua_msg_free(xua); break; default: LOGP(DLSS7, LOGL_ERROR, "Ignoring unknown primitive %u:%u\n", omp->oph.primitive, omp->oph.operation); rc = -1; } msgb_free(omp->oph.msg); return rc; }