9
0
Fork 0
This repository has been archived on 2022-03-30. You can view files and clone it, but cannot push or open issues or pull requests.
cellmgr-ng/src/mtp_layer3.c

693 lines
16 KiB
C

/* MTP layer3 main handling code */
/*
* (C) 2010-2011 by Holger Hans Peter Freyther <zecke@selfish.org>
* (C) 2010-2011 by On-Waves
* All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <mtp_data.h>
#include <osmocom/mtp/mtp_level3.h>
#include <bsc_data.h>
#include <cellmgr_debug.h>
#include <isup_types.h>
#include <counter.h>
#include <osmocom/core/talloc.h>
#include <osmocom/sccp/sccp.h>
#include <arpa/inet.h>
#include <string.h>
static int mtp_int_submit(struct mtp_link_set *set, int opc, int dpc, int sls, int type, const uint8_t *data, unsigned int length);
static void linkset_t18_cb(void *_set);
static void linkset_t20_cb(void *_set);
struct msgb *mtp_msg_alloc(struct mtp_link_set *set)
{
struct mtp_level_3_hdr *hdr;
struct msgb *msg = msgb_alloc_headroom(4096, 128, "mtp-msg");
if (!msg) {
LOGP(DINP, LOGL_ERROR, "Failed to allocate mtp msg\n");
return NULL;
}
msg->l2h = msgb_put(msg, sizeof(*hdr));
hdr = (struct mtp_level_3_hdr *) msg->l2h;
hdr->addr = MTP_ADDR(0x0, set->dpc, set->opc);
hdr->ni = set->ni;
hdr->spare = set->spare;
return msg;
}
static struct msgb *mtp_create_slta(struct mtp_link_set *set, int sls,
struct mtp_level_3_mng *in_mng, int l3_len)
{
struct mtp_level_3_hdr *hdr;
struct mtp_level_3_mng *mng;
struct msgb *out = mtp_msg_alloc(set);
if (!out)
return NULL;
hdr = (struct mtp_level_3_hdr *) out->l2h;
hdr->ser_ind = MTP_SI_MNT_REG_MSG;
hdr->addr = MTP_ADDR(sls, set->dpc, set->opc);
mng = (struct mtp_level_3_mng *) msgb_put(out, sizeof(*mng));
mng->cmn.h0 = MTP_TST_MSG_GRP;
mng->cmn.h1 = MTP_TST_MSG_SLTA;
mng->length = l3_len - 2;
msgb_put(out, mng->length);
memcpy(mng->data, in_mng->data, mng->length);
return out;
}
static struct msgb *mtp_base_alloc(struct mtp_link *link, int msg, int apoc)
{
struct mtp_level_3_hdr *hdr;
struct mtp_level_3_prohib *prb;
struct msgb *out = mtp_msg_alloc(link->set);
if (!out)
return NULL;
hdr = (struct mtp_level_3_hdr *) out->l2h;
hdr->ser_ind = MTP_SI_MNT_SNM_MSG;
hdr->addr = MTP_ADDR(link->first_sls, link->set->dpc, link->set->opc);
prb = (struct mtp_level_3_prohib *) msgb_put(out, sizeof(*prb));
prb->cmn.h0 = MTP_PROHIBIT_MSG_GRP;
prb->cmn.h1 = msg;
prb->apoc = MTP_MAKE_APOC(apoc);
return out;
}
static struct msgb *mtp_tfp_alloc(struct mtp_link *link, int apoc)
{
return mtp_base_alloc(link, MTP_PROHIBIT_MSG_SIG, apoc);
}
static struct msgb *mtp_tfa_alloc(struct mtp_link *link, int apoc)
{
return mtp_base_alloc(link, MTP_PROHIBIT_MSG_TFA, apoc);
}
static struct msgb *mtp_tra_alloc(struct mtp_link *link, int opc)
{
struct mtp_level_3_hdr *hdr;
struct mtp_level_3_cmn *cmn;
struct msgb *out = mtp_msg_alloc(link->set);
if (!out)
return NULL;
hdr = (struct mtp_level_3_hdr *) out->l2h;
hdr->ser_ind = MTP_SI_MNT_SNM_MSG;
hdr->addr = MTP_ADDR(0x0, link->set->dpc, opc);
cmn = (struct mtp_level_3_cmn *) msgb_put(out, sizeof(*cmn));
cmn->h0 = MTP_TRF_RESTR_MSG_GRP;
cmn->h1 = MTP_RESTR_MSG_ALLWED;
return out;
}
static struct msgb *mtp_sccp_alloc_scmg(struct mtp_link_set *set,
int type, int assn, int apoc, int sls)
{
struct sccp_data_unitdata *udt;
struct sccp_con_ctrl_prt_mgt *prt;
struct mtp_level_3_hdr *hdr;
uint8_t *data;
struct msgb *out = mtp_msg_alloc(set);
if (!out)
return NULL;
hdr = (struct mtp_level_3_hdr *) out->l2h;
hdr->ser_ind = MTP_SI_MNT_SCCP;
/* this appears to be round robin or such.. */
hdr->addr = MTP_ADDR(sls % 16, set->dpc, set->sccp_opc);
/* generate the UDT message... libsccp does not offer formating yet */
udt = (struct sccp_data_unitdata *) msgb_put(out, sizeof(*udt));
udt->type = SCCP_MSG_TYPE_UDT;
udt->proto_class = SCCP_PROTOCOL_CLASS_0;
udt->variable_called = 3;
udt->variable_calling = 5;
udt->variable_data = 7;
/* put the called and calling address. It is LV */
data = msgb_put(out, 2 + 1);
data[0] = 2;
data[1] = 0x42;
data[2] = 0x1;
data = msgb_put(out, 2 + 1);
data[0] = 2;
data[1] = 0x42;
data[2] = 0x1;
data = msgb_put(out, 1);
data[0] = sizeof(*prt);
prt = (struct sccp_con_ctrl_prt_mgt *) msgb_put(out, sizeof(*prt));
prt->sst = type;
prt->assn = assn;
prt->apoc = apoc;
prt->mul_ind = 0;
return out;
}
void mtp_link_set_stop(struct mtp_link_set *set)
{
struct mtp_link *lnk;
llist_for_each_entry(lnk, &set->links, entry)
mtp_link_stop_link_test(lnk);
osmo_timer_del(&set->T18);
osmo_timer_del(&set->T20);
set->sccp_up = 0;
set->running = 0;
set->linkset_up = 0;
}
void mtp_link_set_reset(struct mtp_link_set *set)
{
struct mtp_link *lnk;
mtp_link_set_stop(set);
set->running = 1;
llist_for_each_entry(lnk, &set->links, entry)
mtp_link_start_link_test(lnk);
}
/* unused right now but we want to use it again */
int send_tfp(struct mtp_link *link, int apoc)
{
struct msgb *msg;
msg = mtp_tfp_alloc(link, apoc);
if (!msg)
return -1;
mtp_link_submit(link, msg);
return 0;
}
static int send_tra(struct mtp_link *link, int opc)
{
struct msgb *msg;
msg = mtp_tra_alloc(link, opc);
if (!msg)
return -1;
mtp_link_submit(link, msg);
return 0;
}
static int send_tfa(struct mtp_link *link, int opc)
{
struct msgb *msg;
msg = mtp_tfa_alloc(link, opc);
if (!msg)
return -1;
mtp_link_submit(link, msg);
return 0;
}
int mtp_link_verified(struct mtp_link *link)
{
struct mtp_link_set *set = link->set;
/* the link set is already up */
if (set->linkset_up)
return 0;
set->linkset_up = 1;
if (set->timeout_t18 != 0)
osmo_timer_schedule(&set->T18, set->timeout_t18, 0);
if (set->timeout_t20 != 0)
osmo_timer_schedule(&set->T20, set->timeout_t20, 0);
/* More the functionality of a SSP here... */
if (set->sccp_opc != set->opc &&
send_tfa(link, set->sccp_opc) != 0) {
LOGP(DINP, LOGL_ERROR,
"Failed to send TFA for OPC %d on linkset %d.\n", set->sccp_opc, set->nr);
}
if (set->isup_opc != set->opc &&
send_tfa(link, set->isup_opc) != 0) {
LOGP(DINP, LOGL_ERROR,
"Failed to send TFA for OPC %d on linkset %d.\n", set->sccp_opc, set->nr);
}
if (set->timeout_t18 == 0)
linkset_t18_cb(set);
if (set->timeout_t20 == 0)
linkset_t20_cb(set);
return 0;
}
static void linkset_t18_cb(void *_set)
{
struct mtp_link_set *set = _set;
struct mtp_link *link = set->slc[0];
if (!link) {
LOGP(DINP, LOGL_ERROR,
"Linkset restart but no link available on linkset %d\n", set->nr);
osmo_timer_del(&set->T20);
set->linkset_up = 0;
return;
}
/* TODO: now send out routing states */
LOGP(DINP, LOGL_NOTICE, "The linkset %d has collected routing data.\n", set->nr);
set->sccp_up = 1;
}
static void linkset_t20_cb(void *_set)
{
struct mtp_link_set *set = _set;
struct mtp_link *link = set->slc[0];
if (!link) {
LOGP(DINP, LOGL_ERROR,
"Linkset restart but no link available on linkset %d\n", set->nr);
osmo_timer_del(&set->T20);
set->linkset_up = 0;
return;
}
/* Send the TRA for all PCs */
if (send_tra(link, set->opc) != 0)
return;
LOGP(DINP, LOGL_NOTICE,
"The linkset %d/%s is considered running.\n", set->nr, set->name);
return;
}
static int mtp_link_sign_msg(struct mtp_link_set *set, struct mtp_level_3_hdr *hdr, int l3_len)
{
struct mtp_level_3_cmn *cmn;
uint16_t *apc;
if (hdr->ni != set->ni || l3_len < 1) {
LOGP(DINP, LOGL_ERROR, "Unhandled data (ni: %d len: %d)\n",
hdr->ni, l3_len);
return -1;
}
cmn = (struct mtp_level_3_cmn *) &hdr->data[0];
LOGP(DINP, LOGL_DEBUG, "reg msg: h0: 0x%x h1: 0x%x\n",
cmn->h0, cmn->h1);
switch (cmn->h0) {
case MTP_TRF_RESTR_MSG_GRP:
switch (cmn->h1) {
case MTP_RESTR_MSG_ALLWED:
LOGP(DINP, LOGL_INFO,
"Received TRA on linkset %d/%s.\n", set->nr, set->name);
/*
* TODO: routing should be done on a higher level. This should not
* arrive after we expired the timer but we are friendly here and
* respond with a TFA and TRA...
*/
osmo_timer_del(&set->T18);
osmo_timer_del(&set->T20);
linkset_t18_cb(set);
linkset_t20_cb(set);
return 0;
break;
}
break;
case MTP_PROHIBIT_MSG_GRP:
switch (cmn->h1) {
case MTP_PROHIBIT_MSG_SIG:
if (l3_len < 3) {
LOGP(DINP, LOGL_ERROR, "TFP is too short on %d/%s.\n", set->nr, set->name);
return -1;
}
apc = (uint16_t *) &hdr->data[1];
LOGP(DINP, LOGL_INFO,
"TFP for the affected point code %d on %d/%s\n",
*apc, set->nr, set->name);
return 0;
break;
}
break;
}
LOGP(DINP, LOGL_ERROR, "Unknown message:%d/%d %s on %d/%s.\n",
cmn->h0, cmn->h1, osmo_hexdump(&hdr->data[0], l3_len),
set->nr, set->name);
return -1;
}
static int mtp_link_regular_msg(struct mtp_link *link, struct mtp_level_3_hdr *hdr, int l3_len)
{
struct msgb *out;
struct mtp_level_3_mng *mng;
if (hdr->ni != link->set->ni || l3_len < 1) {
LOGP(DINP, LOGL_ERROR, "Unhandled data (ni: %d len: %d)\n",
hdr->ni, l3_len);
return -1;
}
if (MTP_READ_DPC(hdr->addr) != link->set->opc) {
LOGP(DINP, LOGL_ERROR, "MSG for OPC %d not handled on %d/%s\n",
MTP_READ_DPC(hdr->addr), link->set->nr, link->set->name);
return -1;
}
mng = (struct mtp_level_3_mng *) &hdr->data[0];
LOGP(DINP, LOGL_DEBUG, "reg msg: h0: 0x%x h1: 0x%x\n",
mng->cmn.h0, mng->cmn.h1);
switch (mng->cmn.h0) {
case MTP_TST_MSG_GRP:
switch (mng->cmn.h1) {
case MTP_TST_MSG_SLTM:
/* simply respond to the request... */
out = mtp_create_slta(link->set,
MTP_LINK_SLS(hdr->addr),
mng, l3_len);
if (!out)
return -1;
mtp_link_submit(link, out);
return 0;
break;
case MTP_TST_MSG_SLTA:
/* If this link is proven set it up */
if (mtp_link_slta(link, l3_len, mng) == 0)
mtp_link_verified(link);
break;
}
break;
}
return -1;
}
static int mtp_link_sccp_data(struct mtp_link_set *set, struct mtp_level_3_hdr *hdr, struct msgb *msg, int l3_len)
{
struct msgb *out;
struct sccp_con_ctrl_prt_mgt *prt;
struct sccp_parse_result sccp;
int type;
msg->l2h = &hdr->data[0];
if (msgb_l2len(msg) != l3_len) {
LOGP(DINP, LOGL_ERROR, "Size is wrong after playing with the l2h header.\n");
return -1;
}
if (!set->sccp_up) {
LOGP(DINP, LOGL_ERROR, "SCCP traffic is not allowed on %d/%s\n",
set->nr, set->name);
return -1;
}
memset(&sccp, 0, sizeof(sccp));
if (sccp_parse_header(msg, &sccp) != 0) {
LOGP(DINP, LOGL_ERROR, "Failed to parsed SCCP header.\n");
return -1;
}
/* check if it is a SST */
if (sccp_determine_msg_type(msg) == SCCP_MSG_TYPE_UDT
&& msg->l3h[0] == SCCP_SST) {
if (msgb_l3len(msg) != 5) {
LOGP(DINP, LOGL_ERROR,
"SCCP UDT msg of unexpected size: %u\n",
msgb_l3len(msg));
return -1;
}
prt = (struct sccp_con_ctrl_prt_mgt *) &msg->l3h[0];
if (prt->apoc != MTP_MAKE_APOC(set->sccp_opc)) {
LOGP(DINP, LOGL_ERROR, "Unknown APOC: %u/%u on %d/%s\n",
ntohs(prt->apoc), prt->apoc, set->nr, set->name);
type = SCCP_SSP;
} else if (!set->supported_ssn[prt->assn]) {
LOGP(DINP, LOGL_ERROR, "Unknown affected SSN assn: %u on %d/%s\n",
prt->assn, set->nr, set->name);
type = SCCP_SSP;
} else {
type = SCCP_SSA;
}
out = mtp_sccp_alloc_scmg(set, type, prt->assn, prt->apoc,
MTP_LINK_SLS(hdr->addr));
if (!out)
return -1;
mtp_link_submit(set->slc[MTP_LINK_SLS(hdr->addr)], out);
return 0;
}
rate_ctr_inc(&set->ctrg->ctr[MTP_LSET_SCCP_IN_MSG]);
mtp_link_set_forward_sccp(set, msg, MTP_LINK_SLS(hdr->addr));
return 0;
}
int mtp_link_handle_data(struct mtp_link *link, struct msgb *msg)
{
int rc = -1;
struct mtp_level_3_hdr *hdr;
int l3_len;
if (!msg->l2h || msgb_l2len(msg) < sizeof(*hdr))
return -1;
if (!link->set->running) {
LOGP(DINP, LOGL_ERROR,
"Link %d/%s of %d/%s is not running. Call mtp_link_reset first.\n",
link->nr, link->name, link->set->nr, link->set->name);
return -1;
}
hdr = (struct mtp_level_3_hdr *) msg->l2h;
l3_len = msgb_l2len(msg) - sizeof(*hdr);
rate_ctr_inc(&link->ctrg->ctr[MTP_LNK_IN]);
rate_ctr_inc(&link->set->ctrg->ctr[MTP_LSET_TOTA_IN_MSG]);
switch (hdr->ser_ind) {
case MTP_SI_MNT_SNM_MSG:
rc = mtp_link_sign_msg(link->set, hdr, l3_len);
break;
case MTP_SI_MNT_REG_MSG:
rc = mtp_link_regular_msg(link, hdr, l3_len);
break;
case MTP_SI_MNT_SCCP:
rc = mtp_link_sccp_data(link->set, hdr, msg, l3_len);
break;
case MTP_SI_MNT_ISUP:
msg->l3h = &hdr->data[0];
rate_ctr_inc(&link->set->ctrg->ctr[MTP_LSET_IUSP_IN_MSG]);
rc = mtp_link_set_isup(link->set, msg, MTP_LINK_SLS(hdr->addr));
break;
default:
fprintf(stderr, "Unhandled: %u\n", hdr->ser_ind);
break;
}
return rc;
}
int mtp_link_set_submit_sccp_data(struct mtp_link_set *set, int sls, const uint8_t *data, unsigned int length)
{
if (!set->sccp_up) {
LOGP(DINP, LOGL_ERROR, "SCCP msg after TRA and before SSA. Dropping it on %d/%s\n",
set->nr, set->name);
return -1;
}
if (sls == -1) {
sls = set->last_sls;
set->last_sls = (set->last_sls + 1) % 16;
}
rate_ctr_inc(&set->ctrg->ctr[MTP_LSET_SCCP_OUT_MSG]);
return mtp_int_submit(set, set->sccp_opc,
set->sccp_dpc == -1 ? set->dpc : set->sccp_dpc,
sls, MTP_SI_MNT_SCCP, data, length);
}
int mtp_link_set_submit_isup_data(struct mtp_link_set *set, int sls,
const uint8_t *data, unsigned int length)
{
rate_ctr_inc(&set->ctrg->ctr[MTP_LSET_ISUP_OUT_MSG]);
return mtp_int_submit(set, set->isup_opc,
set->isup_dpc == -1 ? set->dpc : set->isup_dpc,
sls, MTP_SI_MNT_ISUP, data, length);
}
int mtp_link_set_send(struct mtp_link_set *set, struct msgb *msg)
{
int sls;
struct mtp_level_3_hdr *hdr;
if (msgb_l2len(msg) < sizeof(*hdr))
return -1;
hdr = (struct mtp_level_3_hdr *) msg->l2h;
sls = MTP_LINK_SLS(hdr->addr);
if (!set->slc[sls])
return -2;
mtp_link_submit(set->slc[sls], msg);
return 0;
}
static int mtp_int_submit(struct mtp_link_set *set, int opc, int dpc,
int sls, int type, const uint8_t *data,
unsigned int length)
{
uint8_t *put_ptr;
struct mtp_level_3_hdr *hdr;
struct msgb *msg;
if (!set->slc[sls % 16])
return -1;
msg = mtp_msg_alloc(set);
if (!msg)
return -1;
hdr = (struct mtp_level_3_hdr *) msg->l2h;
hdr->ser_ind = type;
hdr->addr = MTP_ADDR(sls % 16, dpc, opc);
/* copy the raw sccp data */
put_ptr = msgb_put(msg, length);
memcpy(put_ptr, data, length);
mtp_link_submit(set->slc[sls % 16], msg);
return 0;
}
static struct mtp_link *find_next_link(struct mtp_link_set *set,
struct mtp_link *data)
{
int found = 0;
struct mtp_link *next;
if (llist_empty(&set->links))
return NULL;
if (data == NULL)
found = 1;
/* try to find the next one */
llist_for_each_entry(next, &set->links, entry) {
if (found && next->available)
return next;
if (next == data)
found = 1;
}
/* try to find any one */
llist_for_each_entry(next, &set->links, entry)
if (next->available)
return next;
return NULL;
}
void mtp_link_set_init_slc(struct mtp_link_set *set)
{
struct mtp_link *link = NULL, *tmp;
int i;
llist_for_each_entry(tmp, &set->links, entry)
tmp->first_sls = 100;
for (i = 0; i < ARRAY_SIZE(set->slc); ++i) {
link = find_next_link(set, link);
set->slc[i] = link;
if (link && i < link->first_sls)
link->first_sls = i;
}
}
struct mtp_link_set *mtp_link_set_alloc(struct bsc_data *bsc)
{
struct mtp_link_set *set;
set = talloc_zero(bsc, struct mtp_link_set);
if (!set)
return NULL;
set->ctrg = rate_ctr_group_alloc(set,
mtp_link_set_rate_ctr_desc(),
bsc->num_linksets + 1);
if (!set->ctrg) {
LOGP(DINP, LOGL_ERROR, "Failed to allocate counter.\n");
return NULL;
}
set->ni = MTP_NI_NATION_NET;
INIT_LLIST_HEAD(&set->links);
set->nr = bsc->num_linksets++;
set->sccp_opc = set->isup_opc = -1;
set->sccp_dpc = set->isup_dpc = -1;
set->pcap_fd = bsc->pcap_fd;
set->bsc = bsc;
/* timeout code */
set->timeout_t18 = 15;
set->timeout_t20 = 16;
set->T18.cb = linkset_t18_cb;
set->T18.data = set;
set->T20.cb = linkset_t20_cb;
set->T20.data = set;
llist_add_tail(&set->entry, &bsc->linksets);
return set;
}
struct mtp_link_set *mtp_link_set_num(struct bsc_data *bsc, int num)
{
struct mtp_link_set *set;
llist_for_each_entry(set, &bsc->linksets, entry)
if (set->nr == num)
return set;
return NULL;
}