libosmo-sccp/src/ipa.c

322 lines
9.5 KiB
C

/* implementation of IPA/SCCPlite transport */
/* (C) 2015-2017 by Harald Welte <laforge@gnumonks.org>
* 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 <http://www.gnu.org/licenses/>.
*
*/
#include <stdint.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/write_queue.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/timer.h>
#include <osmocom/core/socket.h>
#include <osmocom/core/byteswap.h>
#include <osmocom/gsm/ipa.h>
#include <osmocom/gsm/protocol/ipaccess.h>
//#include <osmocom/netif/stream.h>
#include <osmocom/netif/ipa.h>
#include <osmocom/sigtran/xua_msg.h>
#include <osmocom/sigtran/mtp_sap.h>
#include <osmocom/sigtran/sccp_sap.h>
#include <osmocom/sigtran/osmo_ss7.h>
#include <osmocom/sigtran/protocol/m3ua.h>
#include <osmocom/sigtran/protocol/sua.h>
#include <osmocom/sigtran/protocol/mtp.h>
#include "xua_internal.h"
#include "ss7_internal.h"
#include "xua_asp_fsm.h"
/*! \brief Send a given xUA message via a given IPA "Application Server"
* \param[in] as Application Server through which to send \a xua
* \param[in] xua xUA message to be sent
* \return 0 on success; negative on error */
int ipa_tx_xua_as(struct osmo_ss7_as *as, struct xua_msg *xua)
{
struct xua_msg_part *data_ie;
struct m3ua_data_hdr *data_hdr;
struct msgb *msg;
unsigned int src_len;
const uint8_t *src;
uint8_t *dst;
OSMO_ASSERT(as->cfg.proto == OSMO_SS7_ASP_PROT_IPA);
/* we're actually only interested in the data part */
data_ie = xua_msg_find_tag(xua, M3UA_IEI_PROT_DATA);
if (!data_ie || data_ie->len < sizeof(struct m3ua_data_hdr))
return -1;
data_hdr = (struct m3ua_data_hdr *) data_ie->dat;
if (data_hdr->si != MTP_SI_SCCP) {
LOGPAS(as, DLSS7, LOGL_ERROR, "Cannot transmit non-SCCP SI (%u) to IPA peer\n",
data_hdr->si);
return -1;
}
/* and even the data part still has the header prepended */
src = data_ie->dat + sizeof(struct m3ua_data_hdr);
src_len = data_ie->len - sizeof(struct m3ua_data_hdr);
/* sufficient headroom for osmo_ipa_msg_push_header() */
msg = ipa_msg_alloc(16);
if (!msg)
return -1;
dst = msgb_put(msg, src_len);
memcpy(dst, src, src_len);
/* TODO: if we ever need something beyond SCCP, we can use the
* M3UA SIO to determine the protocol */
osmo_ipa_msg_push_header(msg, IPAC_PROTO_SCCP);
return xua_as_transmit_msg(as, msg);
}
static int ipa_rx_msg_ccm(struct osmo_ss7_asp *asp, struct msgb *msg)
{
uint8_t msg_type = msg->l2h[0];
LOGPASP(asp, DLSS7, LOGL_DEBUG, "%s:%s\n", __func__, msgb_hexdump(msg));
/* Convert CCM into events to the IPA_ASP_FSM */
switch (msg_type) {
case IPAC_MSGT_ID_ACK:
osmo_fsm_inst_dispatch(asp->fi, IPA_ASP_E_ID_ACK, msg);
break;
case IPAC_MSGT_ID_RESP:
osmo_fsm_inst_dispatch(asp->fi, IPA_ASP_E_ID_RESP, msg);
break;
case IPAC_MSGT_ID_GET:
osmo_fsm_inst_dispatch(asp->fi, IPA_ASP_E_ID_GET, msg);
break;
case IPAC_MSGT_PING:
osmo_fsm_inst_dispatch(asp->fi, XUA_ASP_E_ASPSM_BEAT, msg);
break;
case IPAC_MSGT_PONG:
osmo_fsm_inst_dispatch(asp->fi, XUA_ASP_E_ASPSM_BEAT_ACK, msg);
break;
default:
LOGPASP(asp, DLSS7, LOGL_NOTICE, "Unknown CCM Message 0x%02x: %s\n",
msg_type, msgb_hexdump(msg));
return -1;
}
msgb_free(msg);
return 0;
}
struct osmo_ss7_as *ipa_find_as_for_asp(struct osmo_ss7_asp *asp)
{
struct osmo_ss7_as *as;
/* in the IPA case, we assume there is a 1:1 mapping between the
* ASP and the AS. An AS without ASP means there is no
* connection, and an ASP without AS means that we don't (yet?)
* know the identity of the peer */
llist_for_each_entry(as, &asp->inst->as_list, list) {
if (osmo_ss7_as_has_asp(as, asp))
return as;
}
return NULL;
}
/* Patch a SCCP message and add point codes to Called/Calling Party (if missing) */
static struct msgb *patch_sccp_with_pc(struct osmo_ss7_asp *asp, struct msgb *sccp_msg_in,
uint32_t opc, uint32_t dpc)
{
struct osmo_sccp_addr addr;
struct msgb *sccp_msg_out;
struct xua_msg *sua;
int rc;
/* start by converting SCCP to SUA */
sua = osmo_sccp_to_xua(sccp_msg_in);
if (!sua) {
LOGPASP(asp, DLSS7, LOGL_ERROR, "Couldn't convert SCCP to SUA: %s\n",
msgb_hexdump(sccp_msg_in));
msgb_free(sccp_msg_in);
return NULL;
}
/* free the input message and work with SUA version instead */
msgb_free(sccp_msg_in);
rc = sua_addr_parse(&addr, sua, SUA_IEI_DEST_ADDR);
switch (rc) {
case 0:
if (addr.presence & OSMO_SCCP_ADDR_T_PC)
break;
/* if there's no point code in dest_addr, add one */
addr.presence |= OSMO_SCCP_ADDR_T_PC;
addr.pc = dpc;
xua_msg_free_tag(sua, SUA_IEI_DEST_ADDR);
xua_msg_add_sccp_addr(sua, SUA_IEI_DEST_ADDR, &addr);
break;
case -ENODEV: /* no destination address in message */
break;
default: /* some other error */
xua_msg_free(sua);
return NULL;
}
rc = sua_addr_parse(&addr, sua, SUA_IEI_SRC_ADDR);
switch (rc) {
case 0:
if (addr.presence & OSMO_SCCP_ADDR_T_PC)
break;
/* if there's no point code in src_addr, add one */
addr.presence |= OSMO_SCCP_ADDR_T_PC;
addr.pc = opc;
xua_msg_free_tag(sua, SUA_IEI_SRC_ADDR);
xua_msg_add_sccp_addr(sua, SUA_IEI_SRC_ADDR, &addr);
break;
case -ENODEV: /* no source address in message */
break;
default: /* some other error */
xua_msg_free(sua);
return NULL;
}
/* re-encode SUA to SCCP and return */
sccp_msg_out = osmo_sua_to_sccp(sua);
if (!sccp_msg_out)
LOGPASP(asp, DLSS7, LOGL_ERROR, "Couldn't re-encode SUA to SCCP\n");
xua_msg_free(sua);
return sccp_msg_out;
}
static int ipa_rx_msg_sccp(struct osmo_ss7_asp *asp, struct msgb *msg, uint8_t sls)
{
int rc;
struct m3ua_data_hdr data_hdr;
struct xua_msg *xua;
struct osmo_ss7_as *as = ipa_find_as_for_asp(asp);
uint32_t opc, dpc;
if (!as) {
LOGPASP(asp, DLSS7, LOGL_ERROR, "Rx message for IPA ASP without AS?!\n");
msgb_free(msg);
return -1;
}
rate_ctr_inc2(as->ctrg, SS7_AS_CTR_RX_MSU_TOTAL);
/* pull the IPA header */
msgb_pull_to_l2(msg);
/* We have received an IPA-encapsulated SCCP message, without
* any MTP routing label. Furthermore, the SCCP Called/Calling
* Party are SSN-only, with no GT or PC. This means we have no
* real idea where it came from, nor where it goes to. We could
* simply treat it as being for the local point code, but then
* this means that we would have to implement SCCP connection
* coupling in order to route the connections to any other point
* code. The reason for this is the lack of addressing
* information inside the non-CR/CC connection oriented
* messages.
*
* The only other alternative we have is to simply have a
* STP (server) side configuration that specifies which point
* code those messages are to be routed to, and then use this
* 'override DPC' in the routing decision. We could do the same
* for the source point code to ensure responses are routed back
* to us. This is all quite ugly, but then what can we do :/
*/
/* First, determine the DPC and OPC to use */
if (asp->cfg.is_server) {
/* Source: the PC of the routing key */
opc = as->cfg.routing_key.pc;
/* Destination: Based on VTY config */
dpc = as->cfg.pc_override.dpc;
} else {
/* Source: Based on VTY config */
opc = as->cfg.pc_override.dpc;
/* Destination: PC of the routing key */
dpc = as->cfg.routing_key.pc;
}
/* Second, patch this into the SCCP message */
if (as->cfg.pc_override.sccp_mode == OSMO_SS7_PATCH_BOTH) {
msg = patch_sccp_with_pc(asp, msg, opc, dpc);
if (!msg) {
LOGPASP(asp, DLSS7, LOGL_ERROR, "Unable to patch PC into SCCP message; dropping\n");
return -1;
}
}
/* Third, create a MTP3/M3UA label with those point codes */
memset(&data_hdr, 0, sizeof(data_hdr));
data_hdr.si = MTP_SI_SCCP;
data_hdr.opc = osmo_htonl(opc);
data_hdr.dpc = osmo_htonl(dpc);
data_hdr.sls = sls;
data_hdr.ni = as->inst->cfg.network_indicator;
/* Create M3UA message in XUA structure */
xua = m3ua_xfer_from_data(&data_hdr, msgb_l2(msg), msgb_l2len(msg));
msgb_free(msg);
/* Update xua->mtp with values from data_hdr */
m3ua_dh_to_xfer_param(&xua->mtp, &data_hdr);
/* Pass on as if we had received it from an M3UA ASP */
rc = m3ua_hmdc_rx_from_l2(asp->inst, xua);
xua_msg_free(xua);
return rc;
}
/*! \brief process M3UA message received from socket
* \param[in] asp Application Server Process receiving \a msg
* \param[in] msg received message buffer. Callee takes ownership!
* \param[in] sls The SLS (signaling link selector) field to use in the generated M3UA header
* \returns 0 on success; negative on error */
int ipa_rx_msg(struct osmo_ss7_asp *asp, struct msgb *msg, uint8_t sls)
{
struct ipaccess_head *hh;
int rc;
OSMO_ASSERT(asp->cfg.proto == OSMO_SS7_ASP_PROT_IPA);
/* osmo_ipa_process_msg() will already have verified length
* consistency and set up l2h pointer */
hh = (struct ipaccess_head *) msg->l1h;
switch (hh->proto) {
case IPAC_PROTO_IPACCESS:
rc = ipa_rx_msg_ccm(asp, msg);
break;
case IPAC_PROTO_SCCP:
rc = ipa_rx_msg_sccp(asp, msg, sls);
break;
default:
rc = ss7_asp_rx_unknown(asp, hh->proto, msg);
}
return rc;
}