808 lines
26 KiB
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,
|
|
};
|