osmo-bsc/src/osmo-bsc/lb.c

808 lines
26 KiB
C

/* Lb interface low level SCCP handling */
/*
* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
*
* All Rights Reserved
*
* Author: Neels Hofmeyr <neels@hofmeyr.de>
*
* 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 <osmocom/bsc/lb.h>
#include <osmocom/gsm/bssmap_le.h>
#include <osmocom/sigtran/sccp_helpers.h>
#include <osmocom/bsc/gsm_data.h>
#include <osmocom/bsc/vty.h>
#include <osmocom/bsc/debug.h>
#include <osmocom/bsc/osmo_bsc_sigtran.h>
#include <osmocom/bsc/lcs_loc_req.h>
#include <osmocom/bsc/bssmap_reset.h>
#include <osmocom/bsc/gsm_data.h>
/* Send reset to SMLC */
int bssmap_le_tx_reset(void)
{
struct osmo_ss7_instance *ss7;
struct msgb *msg;
struct bssap_le_pdu reset = {
.discr = BSSAP_LE_MSG_DISCR_BSSMAP_LE,
.bssmap_le = {
.msg_type = BSSMAP_LE_MSGT_RESET,
.reset = GSM0808_CAUSE_EQUIPMENT_FAILURE,
},
};
if (!bsc_gsmnet->smlc->sccp_user) {
LOGP(DRESET, LOGL_DEBUG, "Not sending RESET to SMLC, Lb link down\n");
return -1;
}
ss7 = osmo_ss7_instance_find(bsc_gsmnet->smlc->cs7_instance);
OSMO_ASSERT(ss7);
LOGP(DRESET, LOGL_INFO, "Sending RESET to SMLC: %s\n", osmo_sccp_addr_name(ss7, &bsc_gsmnet->smlc->smlc_addr));
msg = osmo_bssap_le_enc(&reset);
rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->smlc->ctrs, SMLC_CTR_BSSMAP_LE_TX_UDT_RESET));
return osmo_sccp_tx_unitdata_msg(bsc_gsmnet->smlc->sccp_user, &bsc_gsmnet->smlc->bsc_addr,
&bsc_gsmnet->smlc->smlc_addr, msg);
}
/* Send reset-ack to SMLC */
int bssmap_le_tx_reset_ack(void)
{
struct osmo_ss7_instance *ss7;
struct msgb *msg;
struct bssap_le_pdu reset_ack = {
.discr = BSSAP_LE_MSG_DISCR_BSSMAP_LE,
.bssmap_le = {
.msg_type = BSSMAP_LE_MSGT_RESET_ACK,
},
};
ss7 = osmo_ss7_instance_find(bsc_gsmnet->smlc->cs7_instance);
OSMO_ASSERT(ss7);
LOGP(DRESET, LOGL_NOTICE, "Sending RESET ACK to SMLC: %s\n", osmo_sccp_addr_name(ss7, &bsc_gsmnet->smlc->smlc_addr));
msg = osmo_bssap_le_enc(&reset_ack);
rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->smlc->ctrs, SMLC_CTR_BSSMAP_LE_TX_UDT_RESET_ACK));
return osmo_sccp_tx_unitdata_msg(bsc_gsmnet->smlc->sccp_user, &bsc_gsmnet->smlc->bsc_addr,
&bsc_gsmnet->smlc->smlc_addr, msg);
}
static int handle_unitdata_from_smlc(const struct osmo_sccp_addr *smlc_addr, struct msgb *msg,
const struct osmo_sccp_user *scu)
{
struct osmo_ss7_instance *ss7;
struct bssap_le_pdu bssap_le;
struct osmo_bssap_le_err *err = NULL;
struct rate_ctr_group *ctrg = bsc_gsmnet->smlc->ctrs;
ss7 = osmo_sccp_get_ss7(osmo_sccp_get_sccp(scu));
OSMO_ASSERT(ss7);
if (osmo_sccp_addr_cmp(smlc_addr, &bsc_gsmnet->smlc->smlc_addr, OSMO_SCCP_ADDR_T_MASK)) {
LOGP(DLCS, LOGL_ERROR, "Rx BSSMAP-LE UnitData from unknown remote address: %s\n",
osmo_sccp_addr_name(ss7, smlc_addr));
rate_ctr_inc(rate_ctr_group_get_ctr(ctrg, SMLC_CTR_BSSMAP_LE_RX_UNKNOWN_PEER));
return -EINVAL;
}
if (osmo_bssap_le_dec(&bssap_le, &err, msg, msg)) {
LOGP(DLCS, LOGL_ERROR, "Rx BSSAP-LE UnitData with error: %s\n", err->logmsg);
rate_ctr_inc(rate_ctr_group_get_ctr(ctrg, SMLC_CTR_BSSMAP_LE_RX_UDT_ERR_INVALID_MSG));
return -EINVAL;
}
if (bssap_le.discr != BSSAP_LE_MSG_DISCR_BSSMAP_LE) {
LOGP(DLCS, LOGL_ERROR, "Rx BSSAP-LE: discr %d not implemented\n", bssap_le.discr);
return -ENOTSUP;
}
switch (bssap_le.bssmap_le.msg_type) {
case BSSMAP_LE_MSGT_RESET:
rate_ctr_inc(rate_ctr_group_get_ctr(ctrg, SMLC_CTR_BSSMAP_LE_RX_UDT_RESET));
LOGP(DLCS, LOGL_NOTICE, "RESET from SMLC: %s\n", osmo_sccp_addr_name(ss7, smlc_addr));
return osmo_fsm_inst_dispatch(bsc_gsmnet->smlc->bssmap_reset->fi, BSSMAP_RESET_EV_RX_RESET, NULL);
case BSSMAP_LE_MSGT_RESET_ACK:
rate_ctr_inc(rate_ctr_group_get_ctr(ctrg, SMLC_CTR_BSSMAP_LE_RX_UDT_RESET_ACK));
LOGP(DLCS, LOGL_NOTICE, "RESET-ACK from SMLC: %s\n", osmo_sccp_addr_name(ss7, smlc_addr));
return osmo_fsm_inst_dispatch(bsc_gsmnet->smlc->bssmap_reset->fi, BSSMAP_RESET_EV_RX_RESET_ACK, NULL);
default:
rate_ctr_inc(rate_ctr_group_get_ctr(ctrg, SMLC_CTR_BSSMAP_LE_RX_UDT_ERR_INVALID_MSG));
LOGP(DLCS, LOGL_ERROR, "Rx unimplemented UDT message type %s\n",
osmo_bssap_le_pdu_to_str_c(OTC_SELECT, &bssap_le));
return -EINVAL;
}
}
static int sccp_sap_up(struct osmo_prim_hdr *oph, void *_scu)
{
struct osmo_scu_prim *scu_prim = (struct osmo_scu_prim *)oph;
struct osmo_sccp_user *scu = _scu;
struct osmo_sccp_instance *sccp = osmo_sccp_get_sccp(scu);
struct bsc_sccp_inst *bsc_sccp = osmo_sccp_get_priv(sccp);
struct gsm_subscriber_connection *conn;
int rc = 0;
switch (OSMO_PRIM_HDR(&scu_prim->oph)) {
case OSMO_PRIM(OSMO_SCU_PRIM_N_UNITDATA, PRIM_OP_INDICATION):
/* Handle inbound UnitData */
DEBUGP(DLCS, "N-UNITDATA.ind(%s)\n", osmo_hexdump(msgb_l2(oph->msg), msgb_l2len(oph->msg)));
rc = handle_unitdata_from_smlc(&scu_prim->u.unitdata.calling_addr, oph->msg, scu);
break;
case OSMO_PRIM(OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_INDICATION):
/* Handle inbound connections. A Location Request is always started on the A interface, and OsmoBSC
* forwards this to the SMLC by performing an N-CONNECT from BSC -> SMLC. This is the reverse
* direction: N-CONNECT from SMLC -> BSC, which should never happen. */
LOGP(DLCS, LOGL_ERROR, "N-CONNECT.ind(X->%u): inbound connect from SMLC is not expected to happen\n",
scu_prim->u.connect.conn_id);
rc = osmo_sccp_tx_disconn(scu, scu_prim->u.connect.conn_id, &scu_prim->u.connect.called_addr, 0);
break;
case OSMO_PRIM(OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_CONFIRM):
/* Handle inbound confirmation of outbound connection */
DEBUGP(DLCS, "N-CONNECT.cnf(%u)\n", scu_prim->u.connect.conn_id);
conn = bsc_sccp_inst_get_gscon_by_conn_id(bsc_sccp, scu_prim->u.connect.conn_id);
if (conn) {
conn->lcs.lb.state = SUBSCR_SCCP_ST_CONNECTED;
if (msgb_l2len(oph->msg) > 0) {
rc = lcs_loc_req_rx_bssmap_le(conn, oph->msg);
}
} else {
LOGP(DLCS, LOGL_ERROR, "N-CONNECT.cfm(%u) for unknown conn\n", scu_prim->u.connect.conn_id);
rc = -EINVAL;
}
break;
case OSMO_PRIM(OSMO_SCU_PRIM_N_DATA, PRIM_OP_INDICATION):
/* Handle incoming connection oriented data */
DEBUGP(DLCS, "N-DATA.ind(%u)\n", scu_prim->u.data.conn_id);
conn = bsc_sccp_inst_get_gscon_by_conn_id(bsc_sccp, scu_prim->u.data.conn_id);
if (!conn) {
LOGP(DLCS, LOGL_ERROR, "N-DATA.ind(%u) for unknown conn_id\n", scu_prim->u.data.conn_id);
rc = -EINVAL;
} else if (conn->lcs.lb.state != SUBSCR_SCCP_ST_CONNECTED) {
LOGP(DLCS, LOGL_ERROR, "N-DATA.ind(%u) for conn that is not confirmed\n",
scu_prim->u.data.conn_id);
rc = -EINVAL;
} else {
rc = lcs_loc_req_rx_bssmap_le(conn, oph->msg);
}
break;
case OSMO_PRIM(OSMO_SCU_PRIM_N_DISCONNECT, PRIM_OP_INDICATION):
DEBUGP(DLCS, "N-DISCONNECT.ind(%u, %s, cause=%i)\n", scu_prim->u.disconnect.conn_id,
osmo_hexdump(msgb_l2(oph->msg), msgb_l2len(oph->msg)),
scu_prim->u.disconnect.cause);
/* indication of disconnect */
conn = bsc_sccp_inst_get_gscon_by_conn_id(bsc_sccp, scu_prim->u.disconnect.conn_id);
if (!conn) {
LOGP(DLCS, LOGL_ERROR, "N-DISCONNECT.ind for unknown conn_id %u\n",
scu_prim->u.disconnect.conn_id);
rc = -EINVAL;
} else {
conn->lcs.lb.state = SUBSCR_SCCP_ST_NONE;
if (msgb_l2len(oph->msg) > 0) {
rc = lcs_loc_req_rx_bssmap_le(conn, oph->msg);
}
}
break;
default:
LOGP(DLCS, LOGL_ERROR, "Unhandled SIGTRAN operation %s on primitive %u\n",
get_value_string(osmo_prim_op_names, oph->operation), oph->primitive);
break;
}
msgb_free(oph->msg);
return rc;
}
static int lb_open_conn(struct gsm_subscriber_connection *conn, struct msgb *msg)
{
struct osmo_ss7_instance *ss7;
uint32_t conn_id;
int rc;
struct bsc_sccp_inst *bsc_sccp = osmo_sccp_get_priv(bsc_gsmnet->smlc->sccp);
OSMO_ASSERT(conn);
OSMO_ASSERT(msg);
if (conn->lcs.lb.state != SUBSCR_SCCP_ST_NONE) {
LOGPFSMSL(conn->fi, DLCS, LOGL_ERROR,
"Cannot open BSSMAP-LE conn to SMLC, another conn is still active for this subscriber\n");
return -EINVAL;
}
conn_id = bsc_sccp_inst_next_conn_id(bsc_sccp);
if (conn_id == SCCP_CONN_ID_UNSET) {
LOGPFSMSL(conn->fi, DLCS, LOGL_ERROR, "Unable to allocate SCCP Connection ID for BSSMAP-LE to SMLC\n");
return -ENOSPC;
}
conn->lcs.lb.conn_id = conn_id;
ss7 = osmo_ss7_instance_find(bsc_gsmnet->smlc->cs7_instance);
OSMO_ASSERT(ss7);
LOGPFSMSL(conn->fi, DLCS, LOGL_INFO, "Opening new SCCP connection (id=%u) to SMLC: %s\n", conn_id,
osmo_sccp_addr_name(ss7, &bsc_gsmnet->smlc->smlc_addr));
rc = osmo_sccp_tx_conn_req_msg(bsc_gsmnet->smlc->sccp_user, conn_id, &bsc_gsmnet->smlc->bsc_addr,
&bsc_gsmnet->smlc->smlc_addr, msg);
if (rc >= 0)
rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->smlc->ctrs, SMLC_CTR_BSSMAP_LE_TX_SUCCESS));
else
rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->smlc->ctrs, SMLC_CTR_BSSMAP_LE_TX_ERR_SEND));
if (rc >= 0)
conn->lcs.lb.state = SUBSCR_SCCP_ST_WAIT_CONN_CONF;
return rc;
}
void lb_close_conn(struct gsm_subscriber_connection *conn)
{
if (conn->lcs.lb.state == SUBSCR_SCCP_ST_NONE)
return;
osmo_sccp_tx_disconn(bsc_gsmnet->smlc->sccp_user, conn->lcs.lb.conn_id, &bsc_gsmnet->smlc->bsc_addr, 0);
conn->lcs.lb.state = SUBSCR_SCCP_ST_NONE;
}
/* Send data to SMLC, take ownership of *msg */
int lb_send(struct gsm_subscriber_connection *conn, const struct bssap_le_pdu *bssap_le)
{
int rc;
struct msgb *msg;
OSMO_ASSERT(conn);
if (!bssmap_reset_is_conn_ready(bsc_gsmnet->smlc->bssmap_reset)) {
LOGPFSMSL(conn->fi, DLCS, LOGL_ERROR, "Lb link to SMLC is not ready (no RESET-ACK), cannot send %s\n",
osmo_bssap_le_pdu_to_str_c(OTC_SELECT, bssap_le));
/* If the remote side was lost, make sure that the SCCP conn is discarded in the local state and towards
* the STP. */
lb_close_conn(conn);
return -EINVAL;
}
msg = osmo_bssap_le_enc(bssap_le);
if (!msg) {
LOGPFSMSL(conn->fi, DLCS, LOGL_ERROR, "Failed to encode %s\n",
osmo_bssap_le_pdu_to_str_c(OTC_SELECT, bssap_le));
return -EINVAL;
}
if (conn->lcs.lb.state == SUBSCR_SCCP_ST_NONE) {
rc = lb_open_conn(conn, msg);
goto count_tx;
}
LOGPFSMSL(conn->fi, DLCS, LOGL_DEBUG, "Tx %s\n", osmo_bssap_le_pdu_to_str_c(OTC_SELECT, bssap_le));
rc = osmo_sccp_tx_data_msg(bsc_gsmnet->smlc->sccp_user, conn->lcs.lb.conn_id, msg);
if (rc >= 0)
rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->smlc->ctrs, SMLC_CTR_BSSMAP_LE_TX_SUCCESS));
else
rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->smlc->ctrs, SMLC_CTR_BSSMAP_LE_TX_ERR_SEND));
count_tx:
if (rc < 0)
return rc;
switch (bssap_le->bssmap_le.msg_type) {
case BSSMAP_LE_MSGT_PERFORM_LOC_REQ:
rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->smlc->ctrs, SMLC_CTR_BSSMAP_LE_TX_DT1_PERFORM_LOCATION_REQUEST));
break;
case BSSMAP_LE_MSGT_PERFORM_LOC_ABORT:
rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->smlc->ctrs, SMLC_CTR_BSSMAP_LE_TX_DT1_PERFORM_LOCATION_ABORT));
break;
case BSSMAP_LE_MSGT_CONN_ORIENTED_INFO:
switch (bssap_le->bssmap_le.conn_oriented_info.apdu.msg_type) {
case BSSLAP_MSGT_TA_RESPONSE:
rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->smlc->ctrs, SMLC_CTR_BSSMAP_LE_TX_DT1_BSSLAP_TA_RESPONSE));
break;
case BSSLAP_MSGT_REJECT:
rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->smlc->ctrs, SMLC_CTR_BSSMAP_LE_TX_DT1_BSSLAP_REJECT));
break;
case BSSLAP_MSGT_RESET:
rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->smlc->ctrs, SMLC_CTR_BSSMAP_LE_TX_DT1_BSSLAP_RESET));
break;
case BSSLAP_MSGT_ABORT:
rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->smlc->ctrs, SMLC_CTR_BSSMAP_LE_TX_DT1_BSSLAP_ABORT));
break;
default:
break;
}
break;
default:
break;
}
return 0;
}
/* Default point-code to be used as local address (BSC) */
#define BSC_DEFAULT_PC "0.23.3"
/* Default point-code to be used as remote address (SMLC) */
#define SMLC_DEFAULT_PC "0.23.6"
#define DEFAULT_ASP_LOCAL_IP "localhost"
#define DEFAULT_ASP_REMOTE_IP "localhost"
void lb_cancel_all(void)
{
struct gsm_subscriber_connection *conn;
llist_for_each_entry(conn, &bsc_gsmnet->subscr_conns, entry)
lcs_loc_req_reset(conn);
};
void lb_reset_link_up(void *data)
{
LOGP(DLCS, LOGL_INFO, "Lb link ready\n");
}
void lb_reset_link_lost(void *data)
{
struct gsm_subscriber_connection *conn;
LOGP(DLCS, LOGL_INFO, "Lb link down\n");
/* Abort all ongoing Location Requests */
llist_for_each_entry(conn, &bsc_gsmnet->subscr_conns, entry)
lcs_loc_req_reset(conn);
};
void lb_reset_tx_reset(void *data)
{
bssmap_le_tx_reset();
}
void lb_reset_tx_reset_ack(void *data)
{
bssmap_le_tx_reset_ack();
}
static void lb_start_reset_fsm(void)
{
struct bssmap_reset_cfg cfg = {
.conn_cfm_failure_threshold = 3,
.ops = {
.tx_reset = lb_reset_tx_reset,
.tx_reset_ack = lb_reset_tx_reset_ack,
.link_up = lb_reset_link_up,
.link_lost = lb_reset_link_lost,
},
};
if (bsc_gsmnet->smlc->bssmap_reset) {
LOGP(DLCS, LOGL_ERROR, "will not allocate a second reset FSM for Lb\n");
return;
}
bsc_gsmnet->smlc->bssmap_reset = bssmap_reset_alloc(bsc_gsmnet, "Lb", &cfg);
}
static void lb_stop_reset_fsm(void)
{
bssmap_reset_term_and_free(bsc_gsmnet->smlc->bssmap_reset);
bsc_gsmnet->smlc->bssmap_reset = NULL;
}
static int lb_start(void)
{
uint32_t default_pc;
struct osmo_ss7_instance *cs7_inst = NULL;
struct osmo_sccp_instance *sccp;
enum osmo_ss7_asp_protocol used_proto = OSMO_SS7_ASP_PROT_M3UA;
char inst_name[32];
const char *smlc_name = "smlc";
struct bsc_sccp_inst *bsc_sccp;
/* Already set up? */
if (bsc_gsmnet->smlc->sccp_user)
return -EALREADY;
LOGP(DLCS, LOGL_INFO, "Starting Lb link\n");
if (!bsc_gsmnet->smlc->cs7_instance_valid) {
bsc_gsmnet->smlc->cs7_instance = 0;
}
cs7_inst = osmo_ss7_instance_find_or_create(tall_bsc_ctx, bsc_gsmnet->smlc->cs7_instance);
OSMO_ASSERT(cs7_inst);
/* If unset, use default SCCP address for the SMLC */
if (!bsc_gsmnet->smlc->smlc_addr.presence)
osmo_sccp_make_addr_pc_ssn(&bsc_gsmnet->smlc->smlc_addr,
osmo_ss7_pointcode_parse(NULL, SMLC_DEFAULT_PC),
OSMO_SCCP_SSN_SMLC_BSSAP_LE);
/* Set up SCCP user and one ASP+AS */
snprintf(inst_name, sizeof(inst_name), "Lb-%u-%s", cs7_inst->cfg.id, osmo_ss7_asp_protocol_name(used_proto));
LOGP(DLCS, LOGL_NOTICE, "Initializing SCCP connection for Lb/%s on cs7 instance %u\n",
osmo_ss7_asp_protocol_name(used_proto), cs7_inst->cfg.id);
/* SS7 Protocol stack */
default_pc = osmo_ss7_pointcode_parse(NULL, BSC_DEFAULT_PC);
sccp = osmo_sccp_simple_client_on_ss7_id(tall_bsc_ctx, cs7_inst->cfg.id, inst_name,
default_pc, used_proto,
0, DEFAULT_ASP_LOCAL_IP,
0, DEFAULT_ASP_REMOTE_IP);
if (!sccp)
return -EINVAL;
bsc_gsmnet->smlc->sccp = sccp;
bsc_sccp = bsc_sccp_inst_alloc(tall_bsc_ctx);
bsc_sccp->sccp = sccp;
osmo_sccp_set_priv(sccp, bsc_sccp);
/* If unset, use default local SCCP address */
if (!bsc_gsmnet->smlc->bsc_addr.presence)
osmo_sccp_local_addr_by_instance(&bsc_gsmnet->smlc->bsc_addr, sccp,
OSMO_SCCP_SSN_BSC_BSSAP_LE);
if (!osmo_sccp_check_addr(&bsc_gsmnet->smlc->bsc_addr, OSMO_SCCP_ADDR_T_SSN | OSMO_SCCP_ADDR_T_PC)) {
LOGP(DLCS, LOGL_ERROR,
"%s %s: invalid local (BSC) SCCP address: %s\n",
inst_name, smlc_name, osmo_sccp_inst_addr_name(sccp, &bsc_gsmnet->smlc->bsc_addr));
return -EINVAL;
}
if (!osmo_sccp_check_addr(&bsc_gsmnet->smlc->smlc_addr, OSMO_SCCP_ADDR_T_SSN | OSMO_SCCP_ADDR_T_PC)) {
LOGP(DLCS, LOGL_ERROR,
"%s %s: invalid remote (SMLC) SCCP address: %s\n",
inst_name, smlc_name, osmo_sccp_inst_addr_name(sccp, &bsc_gsmnet->smlc->smlc_addr));
return -EINVAL;
}
LOGP(DLCS, LOGL_NOTICE, "Lb: %s %s: local (BSC) SCCP address: %s\n",
inst_name, smlc_name, osmo_sccp_inst_addr_name(sccp, &bsc_gsmnet->smlc->bsc_addr));
LOGP(DLCS, LOGL_NOTICE, "Lb: %s %s: remote (SMLC) SCCP address: %s\n",
inst_name, smlc_name, osmo_sccp_inst_addr_name(sccp, &bsc_gsmnet->smlc->smlc_addr));
bsc_gsmnet->smlc->sccp_user = osmo_sccp_user_bind(sccp, smlc_name, sccp_sap_up, bsc_gsmnet->smlc->bsc_addr.ssn);
if (!bsc_gsmnet->smlc->sccp_user)
return -EINVAL;
lb_start_reset_fsm();
return 0;
}
static int lb_stop(void)
{
/* Not set up? */
if (!bsc_gsmnet->smlc->sccp_user)
return -EALREADY;
LOGP(DLCS, LOGL_INFO, "Shutting down Lb link\n");
lb_cancel_all();
lb_stop_reset_fsm();
osmo_sccp_user_unbind(bsc_gsmnet->smlc->sccp_user);
bsc_gsmnet->smlc->sccp_user = NULL;
return 0;
}
int lb_start_or_stop(void)
{
int rc;
if (bsc_gsmnet->smlc->enable) {
rc = lb_start();
switch (rc) {
case 0:
/* all is fine */
break;
case -EALREADY:
/* no need to log about anything */
break;
default:
LOGP(DLCS, LOGL_ERROR, "Failed to start Lb interface (rc=%d)\n", rc);
break;
}
} else {
rc = lb_stop();
switch (rc) {
case 0:
/* all is fine */
break;
case -EALREADY:
/* no need to log about anything */
break;
default:
LOGP(DLCS, LOGL_ERROR, "Failed to stop Lb interface (rc=%d)\n", rc);
break;
}
}
return rc;
}
static void smlc_vty_init(void);
int lb_init(void)
{
OSMO_ASSERT(!bsc_gsmnet->smlc);
bsc_gsmnet->smlc = talloc_zero(bsc_gsmnet, struct smlc_config);
OSMO_ASSERT(bsc_gsmnet->smlc);
bsc_gsmnet->smlc->ctrs = rate_ctr_group_alloc(bsc_gsmnet, &smlc_ctrg_desc, 0);
smlc_vty_init();
return 0;
}
/*********************************************************************************
* VTY Interface (Configuration + Introspection)
*********************************************************************************/
DEFUN(cfg_smlc, cfg_smlc_cmd,
"smlc", "Configure Lb Link to Serving Mobile Location Centre\n")
{
vty->node = SMLC_NODE;
return CMD_SUCCESS;
}
static struct cmd_node smlc_node = {
SMLC_NODE,
"%s(config-smlc)# ",
1,
};
DEFUN(cfg_smlc_enable, cfg_smlc_enable_cmd,
"enable",
"Start up Lb interface connection to the remote SMLC\n")
{
bsc_gsmnet->smlc->enable = true;
if (vty->type != VTY_FILE) {
if (lb_start_or_stop())
vty_out(vty, "%% Error: failed to enable Lb interface%s", VTY_NEWLINE);
}
return CMD_SUCCESS;
}
DEFUN(cfg_smlc_no_enable, cfg_smlc_no_enable_cmd,
"no enable",
NO_STR "Stop Lb interface connection to the remote SMLC\n")
{
bsc_gsmnet->smlc->enable = false;
if (vty->type != VTY_FILE) {
if (lb_start_or_stop())
vty_out(vty, "%% Error: failed to disable Lb interface%s", VTY_NEWLINE);
}
return CMD_SUCCESS;
}
static void enforce_ssn(struct vty *vty, struct osmo_sccp_addr *addr, enum osmo_sccp_ssn want_ssn)
{
if (addr->presence & OSMO_SCCP_ADDR_T_SSN) {
if (addr->ssn != want_ssn)
vty_out(vty,
"setting an SSN (%u) different from the standard (%u) is not allowed, will use standard SSN for address: %s%s",
addr->ssn, want_ssn, osmo_sccp_addr_dump(addr), VTY_NEWLINE);
}
addr->presence |= OSMO_SCCP_ADDR_T_SSN;
addr->ssn = want_ssn;
}
/* Prevent mixing addresses from different CS7 instances */
bool smlc_set_cs7_instance(struct vty *vty, const char *from_vty_cmd, const char *from_addr,
struct osmo_ss7_instance *ss7)
{
if (bsc_gsmnet->smlc->cs7_instance_valid) {
if (bsc_gsmnet->smlc->cs7_instance != ss7->cfg.id) {
LOGP(DLCS, LOGL_ERROR,
"%s: expecting address from cs7 instance %u, but '%s' is from %u\n",
from_vty_cmd, bsc_gsmnet->smlc->cs7_instance, from_addr, ss7->cfg.id);
vty_out(vty, "Error:"
" %s: expecting address from cs7 instance %u, but '%s' is from %u%s",
from_vty_cmd, bsc_gsmnet->smlc->cs7_instance, from_addr, ss7->cfg.id, VTY_NEWLINE);
return false;
}
} else {
bsc_gsmnet->smlc->cs7_instance = ss7->cfg.id;
bsc_gsmnet->smlc->cs7_instance_valid = true;
LOGP(DLCS, LOGL_NOTICE, "Lb interface is using cs7 instance %u\n", bsc_gsmnet->smlc->cs7_instance);
}
return true;
}
DEFUN(cfg_smlc_cs7_bsc_addr,
cfg_smlc_cs7_bsc_addr_cmd,
"bsc-addr NAME",
"Local SCCP address of this BSC towards the SMLC\n" "Name of cs7 addressbook entry\n")
{
const char *bsc_addr_name = argv[0];
struct osmo_ss7_instance *ss7;
ss7 = osmo_sccp_addr_by_name(&bsc_gsmnet->smlc->bsc_addr, bsc_addr_name);
if (!ss7) {
vty_out(vty, "Error: No such SCCP addressbook entry: '%s'%s", bsc_addr_name, VTY_NEWLINE);
return CMD_ERR_INCOMPLETE;
}
if (!smlc_set_cs7_instance(vty, "smlc / bsc-addr", bsc_addr_name, ss7))
return CMD_WARNING;
enforce_ssn(vty, &bsc_gsmnet->smlc->bsc_addr, OSMO_SCCP_SSN_BSC_BSSAP_LE);
bsc_gsmnet->smlc->bsc_addr_name = talloc_strdup(bsc_gsmnet, bsc_addr_name);
return CMD_SUCCESS;
}
DEFUN(cfg_smlc_cs7_smlc_addr,
cfg_smlc_cs7_smlc_addr_cmd,
"smlc-addr NAME",
"Remote SCCP address of the SMLC\n" "Name of cs7 addressbook entry\n")
{
const char *smlc_addr_name = argv[0];
struct osmo_ss7_instance *ss7;
ss7 = osmo_sccp_addr_by_name(&bsc_gsmnet->smlc->smlc_addr, smlc_addr_name);
if (!ss7) {
vty_out(vty, "Error: No such SCCP addressbook entry: '%s'%s", smlc_addr_name, VTY_NEWLINE);
return CMD_ERR_INCOMPLETE;
}
if (!smlc_set_cs7_instance(vty, "smlc / smlc-addr", smlc_addr_name, ss7))
return CMD_WARNING;
enforce_ssn(vty, &bsc_gsmnet->smlc->smlc_addr, OSMO_SCCP_SSN_SMLC_BSSAP_LE);
bsc_gsmnet->smlc->smlc_addr_name = talloc_strdup(bsc_gsmnet, smlc_addr_name);
return CMD_SUCCESS;
}
static int config_write_smlc(struct vty *vty)
{
/* Nothing to write? */
if (!(bsc_gsmnet->smlc->enable
|| bsc_gsmnet->smlc->bsc_addr_name
|| bsc_gsmnet->smlc->smlc_addr_name))
return 0;
vty_out(vty, "smlc%s", VTY_NEWLINE);
if (bsc_gsmnet->smlc->enable)
vty_out(vty, " enable%s", VTY_NEWLINE);
if (bsc_gsmnet->smlc->bsc_addr_name) {
vty_out(vty, " bsc-addr %s%s",
bsc_gsmnet->smlc->bsc_addr_name, VTY_NEWLINE);
}
if (bsc_gsmnet->smlc->smlc_addr_name) {
vty_out(vty, " smlc-addr %s%s",
bsc_gsmnet->smlc->smlc_addr_name, VTY_NEWLINE);
}
return 0;
}
DEFUN(show_smlc, show_smlc_cmd,
"show smlc",
SHOW_STR "Display state of SMLC / Lb\n")
{
vty_out(vty, "not implemented%s", VTY_NEWLINE);
return CMD_SUCCESS;
}
void smlc_vty_init(void)
{
install_element_ve(&show_smlc_cmd);
install_element(CONFIG_NODE, &cfg_smlc_cmd);
install_node(&smlc_node, config_write_smlc);
install_element(SMLC_NODE, &cfg_smlc_enable_cmd);
install_element(SMLC_NODE, &cfg_smlc_no_enable_cmd);
install_element(SMLC_NODE, &cfg_smlc_cs7_bsc_addr_cmd);
install_element(SMLC_NODE, &cfg_smlc_cs7_smlc_addr_cmd);
}
const struct rate_ctr_desc smlc_ctr_description[] = {
[SMLC_CTR_BSSMAP_LE_RX_UNKNOWN_PEER] = {
"bssmap_le:rx:unknown_peer",
"Number of received BSSMAP-LE messages from an unknown Calling SCCP address"
},
[SMLC_CTR_BSSMAP_LE_RX_UDT_RESET] = {
"bssmap_le:rx:udt:reset:request",
"Number of received BSSMAP-LE UDT RESET messages"
},
[SMLC_CTR_BSSMAP_LE_RX_UDT_RESET_ACK] = {
"bssmap_le:rx:udt:reset:ack",
"Number of received BSSMAP-LE UDT RESET ACKNOWLEDGE messages"
},
[SMLC_CTR_BSSMAP_LE_RX_UDT_ERR_INVALID_MSG] = {
"bssmap_le:rx:udt:err:inval",
"Number of received invalid BSSMAP-LE UDT messages"
},
[SMLC_CTR_BSSMAP_LE_RX_DT1_ERR_INVALID_MSG] = {
"bssmap_le:rx:dt1:err:inval",
"Number of received invalid BSSMAP-LE"
},
[SMLC_CTR_BSSMAP_LE_RX_DT1_PERFORM_LOCATION_RESPONSE_SUCCESS] = {
"bssmap_le:rx:dt1:location:response_success",
"Number of received BSSMAP-LE Perform Location Response messages containing a location estimate"
},
[SMLC_CTR_BSSMAP_LE_RX_DT1_PERFORM_LOCATION_RESPONSE_FAILURE] = {
"bssmap_le:rx:dt1:location:response_failure",
"Number of received BSSMAP-LE Perform Location Response messages containing a failure cause"
},
[SMLC_CTR_BSSMAP_LE_TX_ERR_INVALID_MSG] = {
"bssmap_le:tx:err:inval",
"Number of outgoing BSSMAP-LE messages that are invalid (a bug?)"
},
[SMLC_CTR_BSSMAP_LE_TX_ERR_CONN_NOT_READY] = {
"bssmap_le:tx:err:conn_not_ready",
"Number of BSSMAP-LE messages we tried to send when the connection was not ready yet"
},
[SMLC_CTR_BSSMAP_LE_TX_ERR_SEND] = {
"bssmap_le:tx:err:send",
"Number of socket errors while sending BSSMAP-LE messages"
},
[SMLC_CTR_BSSMAP_LE_TX_SUCCESS] = {
"bssmap_le:tx:success",
"Number of successfully sent BSSMAP-LE messages"
},
[SMLC_CTR_BSSMAP_LE_TX_UDT_RESET] = {
"bssmap_le:tx:udt:reset:request",
"Number of transmitted BSSMAP-LE UDT RESET messages"
},
[SMLC_CTR_BSSMAP_LE_TX_UDT_RESET_ACK] = {
"bssmap_le:tx:udt:reset:ack",
"Number of transmitted BSSMAP-LE UDT RESET ACK messages"
},
[SMLC_CTR_BSSMAP_LE_TX_DT1_PERFORM_LOCATION_REQUEST] = {
"bssmap_le:tx:dt1:location:response",
"Number of transmitted BSSMAP-LE DT1 Perform Location Request messages"
},
[SMLC_CTR_BSSMAP_LE_TX_DT1_PERFORM_LOCATION_ABORT] = {
"bssmap_le:rx:dt1:location:abort",
"Number of received BSSMAP-LE Perform Location Abort messages"
},
[SMLC_CTR_BSSMAP_LE_RX_DT1_BSSLAP_TA_REQUEST] = {
"bssmap_le:rx:dt1:bsslap:ta_request",
"Number of received BSSMAP-LE Connection Oriented Information messages"
" with BSSLAP APDU containing TA Request"
},
[SMLC_CTR_BSSMAP_LE_TX_DT1_BSSLAP_TA_RESPONSE] = {
"bssmap_le:tx:dt1:bsslap:ta_response",
"Number of sent BSSMAP-LE Connection Oriented Information messages"
" with BSSLAP APDU containing TA Response"
},
[SMLC_CTR_BSSMAP_LE_TX_DT1_BSSLAP_REJECT] = {
"bssmap_le:tx:dt1:bsslap:reject",
"Number of sent BSSMAP-LE Connection Oriented Information messages"
" with BSSLAP APDU containing Reject"
},
[SMLC_CTR_BSSMAP_LE_TX_DT1_BSSLAP_RESET] = {
"bssmap_le:tx:dt1:bsslap:reset",
"Number of sent BSSMAP-LE Connection Oriented Information messages"
" with BSSLAP APDU containing Reset"
},
[SMLC_CTR_BSSMAP_LE_TX_DT1_BSSLAP_ABORT] = {
"bssmap_le:tx:dt1:bsslap:abort",
"Number of sent BSSMAP-LE Connection Oriented Information messages"
" with BSSLAP APDU containing Abort"
},
};
const struct rate_ctr_group_desc smlc_ctrg_desc = {
"smlc",
"serving mobile location centre",
OSMO_STATS_CLASS_GLOBAL,
ARRAY_SIZE(smlc_ctr_description),
smlc_ctr_description,
};