aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorNeels Hofmeyr <neels@hofmeyr.de>2020-09-17 17:54:39 +0200
committerNeels Hofmeyr <neels@hofmeyr.de>2020-10-09 00:26:02 +0200
commit4ae338d5b6d737fd37826c02e27e2553cf23e2a3 (patch)
tree4ad03eb36890e9fcf1fce111628b6a4b027a6214 /src
parent1ebc0422d66c23be9294d2dc243dcba62f31ecd4 (diff)
LCS: implement the bulk of Location Services
Diffstat (limited to 'src')
-rw-r--r--src/osmo-bsc/Makefile.am3
-rw-r--r--src/osmo-bsc/bsc_init.c1
-rw-r--r--src/osmo-bsc/bsc_sccp.c9
-rw-r--r--src/osmo-bsc/bsc_subscr_conn_fsm.c60
-rw-r--r--src/osmo-bsc/gsm_04_08_rr.c1
-rw-r--r--src/osmo-bsc/gsm_08_08.c7
-rw-r--r--src/osmo-bsc/gsm_data.c1
-rw-r--r--src/osmo-bsc/handover_fsm.c5
-rw-r--r--src/osmo-bsc/lb.c662
-rw-r--r--src/osmo-bsc/lcs_loc_req.c581
-rw-r--r--src/osmo-bsc/lcs_ta_req.c305
-rw-r--r--src/osmo-bsc/net_init.c1
-rw-r--r--src/osmo-bsc/osmo_bsc_bssap.c16
-rw-r--r--src/osmo-bsc/osmo_bsc_main.c10
-rw-r--r--src/osmo-bsc/osmo_bsc_msc.c6
-rw-r--r--src/osmo-bsc/paging.c30
16 files changed, 1681 insertions, 17 deletions
diff --git a/src/osmo-bsc/Makefile.am b/src/osmo-bsc/Makefile.am
index b0fc181c2..8d109fd08 100644
--- a/src/osmo-bsc/Makefile.am
+++ b/src/osmo-bsc/Makefile.am
@@ -65,9 +65,12 @@ osmo_bsc_SOURCES = \
handover_fsm.c \
handover_logic.c \
handover_vty.c \
+ lb.c \
lchan_fsm.c \
lchan_rtp_fsm.c \
lchan_select.c \
+ lcs_loc_req.c \
+ lcs_ta_req.c \
meas_feed.c \
meas_rep.c \
neighbor_ident.c \
diff --git a/src/osmo-bsc/bsc_init.c b/src/osmo-bsc/bsc_init.c
index 1460af44e..b959c9f4c 100644
--- a/src/osmo-bsc/bsc_init.c
+++ b/src/osmo-bsc/bsc_init.c
@@ -37,6 +37,7 @@
#include <osmocom/bsc/gsm_04_08_rr.h>
#include <osmocom/bsc/neighbor_ident.h>
#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/lb.h>
#include <osmocom/bsc/smscb.h>
#include <osmocom/gsm/protocol/gsm_48_049.h>
diff --git a/src/osmo-bsc/bsc_sccp.c b/src/osmo-bsc/bsc_sccp.c
index 9d4289f3d..0cd1dc9f3 100644
--- a/src/osmo-bsc/bsc_sccp.c
+++ b/src/osmo-bsc/bsc_sccp.c
@@ -23,6 +23,7 @@
#include <osmocom/bsc/gsm_data.h>
#include <osmocom/bsc/bsc_msc_data.h>
+#include <osmocom/bsc/lb.h>
/* We need an unused SCCP conn_id across all SCCP users. */
int bsc_sccp_inst_next_conn_id(struct osmo_sccp_instance *sccp)
@@ -47,7 +48,13 @@ int bsc_sccp_inst_next_conn_id(struct osmo_sccp_instance *sccp)
}
}
- /* Future for LCS: also check Lb-interface conn IDs here */
+ if (bsc_gsmnet->smlc->sccp == sccp
+ && conn->lcs.lb.state != SUBSCR_SCCP_ST_NONE) {
+ if (conn_id == conn->lcs.lb.conn_id) {
+ conn_id_already_used = true;
+ break;
+ }
+ }
}
if (!conn_id_already_used)
diff --git a/src/osmo-bsc/bsc_subscr_conn_fsm.c b/src/osmo-bsc/bsc_subscr_conn_fsm.c
index b127e7f08..5893ea391 100644
--- a/src/osmo-bsc/bsc_subscr_conn_fsm.c
+++ b/src/osmo-bsc/bsc_subscr_conn_fsm.c
@@ -46,6 +46,8 @@
#include <osmocom/bsc/codec_pref.h>
#include <osmocom/mgcp_client/mgcp_client_endpoint_fsm.h>
#include <osmocom/core/byteswap.h>
+#include <osmocom/bsc/lb.h>
+#include <osmocom/bsc/lcs_loc_req.h>
#define S(x) (1 << (x))
@@ -86,6 +88,7 @@ static const struct value_string gscon_fsm_event_names[] = {
{GSCON_EV_LCLS_FAIL, "LCLS_FAIL"},
{GSCON_EV_FORGET_LCHAN, "FORGET_LCHAN"},
{GSCON_EV_FORGET_MGW_ENDPOINT, "FORGET_MGW_ENDPOINT"},
+ {GSCON_EV_LCS_LOC_REQ_END, "LCS_LOC_REQ_END"},
{}
};
@@ -249,22 +252,41 @@ static void handle_bssap_n_connect(struct osmo_fsm_inst *fi, struct osmo_scu_pri
switch (bssmap_type) {
case BSS_MAP_MSG_HANDOVER_RQST:
- /* First off, accept the new conn. */
- osmo_sccp_tx_conn_resp(conn->sccp.msc->a.sccp_user, scu_prim->u.connect.conn_id,
- &scu_prim->u.connect.called_addr, NULL, 0);
+ case BSS_MAP_MSG_PERFORM_LOCATION_RQST:
+ break;
+
+ default:
+ LOGPFSML(fi, LOGL_NOTICE, "No support for N-CONNECT: %s: %s\n",
+ gsm0808_bssap_name(bs->type), gsm0808_bssmap_name(bssmap_type));
+ goto refuse;
+ }
+
+ /* First off, accept the new conn. */
+ if (osmo_sccp_tx_conn_resp(conn->sccp.msc->a.sccp_user, scu_prim->u.connect.conn_id,
+ &scu_prim->u.connect.called_addr, NULL, 0)) {
+ LOGPFSML(fi, LOGL_ERROR, "Cannot send SCCP CONN RESP\n");
+ goto refuse;
+ }
- /* Make sure the conn FSM will osmo_sccp_tx_disconn() on term */
- conn->sccp.state = SUBSCR_SCCP_ST_CONNECTED;
+ /* Make sure the conn FSM will osmo_sccp_tx_disconn() on term */
+ conn->sccp.state = SUBSCR_SCCP_ST_CONNECTED;
+ switch (bssmap_type) {
+ case BSS_MAP_MSG_HANDOVER_RQST:
/* Inter-BSC MT Handover Request, another BSS is handovering to us. */
handover_start_inter_bsc_in(conn, msg);
return;
+
+ case BSS_MAP_MSG_PERFORM_LOCATION_RQST:
+ /* Location Services: MSC asks for location of an IDLE subscriber */
+ conn_fsm_state_chg(ST_ACTIVE);
+ lcs_loc_req_start(conn, msg);
+ return;
+
default:
- break;
+ OSMO_ASSERT(false);
}
- LOGPFSML(fi, LOGL_NOTICE, "No support for N-CONNECT: %s: %s\n",
- gsm0808_bssap_name(bs->type), gsm0808_bssmap_name(bssmap_type));
refuse:
osmo_sccp_tx_disconn(conn->sccp.msc->a.sccp_user, scu_prim->u.connect.conn_id,
&scu_prim->u.connect.called_addr, 0);
@@ -404,6 +426,14 @@ static void gscon_fsm_active(struct osmo_fsm_inst *fi, uint32_t event, void *dat
case GSCON_EV_TX_SCCP:
gscon_sigtran_send(conn, (struct msgb *)data);
break;
+
+ case GSCON_EV_LCS_LOC_REQ_END:
+ /* On the A-interface, there is nothing to do. If there still is an lchan, the conn should stay open. If
+ * not, it is up to the MSC to send a Clear Command.
+ * On the Lb-interface, tear down the SCCP connection. */
+ lb_close_conn(conn);
+ break;
+
default:
OSMO_ASSERT(false);
}
@@ -628,7 +658,8 @@ static const struct osmo_fsm_state gscon_fsm_states[] = {
[ST_ACTIVE] = {
.name = "ACTIVE",
.in_event_mask = EV_TRANSPARENT_SCCP | S(GSCON_EV_ASSIGNMENT_START) |
- S(GSCON_EV_HANDOVER_START),
+ S(GSCON_EV_HANDOVER_START)
+ | S(GSCON_EV_LCS_LOC_REQ_END),
.out_state_mask = S(ST_CLEARING) | S(ST_ASSIGNMENT) |
S(ST_HANDOVER),
.action = gscon_fsm_active,
@@ -656,6 +687,9 @@ void gscon_change_primary_lchan(struct gsm_subscriber_connection *conn, struct g
/* On release, do not receive release events that look like the primary lchan is gone. */
struct gsm_lchan *old_lchan = conn->lchan;
+ if (old_lchan == new_lchan)
+ return;
+
conn->lchan = new_lchan;
conn->lchan->conn = conn;
@@ -738,7 +772,8 @@ void gscon_forget_lchan(struct gsm_subscriber_connection *conn, struct gsm_lchan
if ((conn->fi && conn->fi->state != ST_CLEARING)
&& !conn->lchan
&& !conn->ho.new_lchan
- && !conn->assignment.new_lchan)
+ && !conn->assignment.new_lchan
+ && !conn->lcs.loc_req)
gscon_bssmap_clear(conn, GSM0808_CAUSE_EQUIPMENT_FAILURE);
}
@@ -777,6 +812,9 @@ static void gscon_fsm_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *d
if (conn->ho.fi)
osmo_fsm_inst_dispatch(conn->ho.fi, HO_EV_CONN_RELEASING, NULL);
+ if (conn->lcs.loc_req)
+ osmo_fsm_inst_dispatch(conn->lcs.loc_req->fi, LCS_LOC_REQ_EV_CONN_CLEAR, NULL);
+
OSMO_ASSERT(data);
ccd = data;
if (conn->lchan)
@@ -846,6 +884,8 @@ static void gscon_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cau
lchan_forget_conn(conn->assignment.new_lchan);
lchan_forget_conn(conn->ho.new_lchan);
+ lb_close_conn(conn);
+
if (conn->sccp.state != SUBSCR_SCCP_ST_NONE) {
LOGPFSML(fi, LOGL_DEBUG, "Disconnecting SCCP\n");
struct bsc_msc_data *msc = conn->sccp.msc;
diff --git a/src/osmo-bsc/gsm_04_08_rr.c b/src/osmo-bsc/gsm_04_08_rr.c
index 49ec84860..a44812618 100644
--- a/src/osmo-bsc/gsm_04_08_rr.c
+++ b/src/osmo-bsc/gsm_04_08_rr.c
@@ -1027,7 +1027,6 @@ int gsm0408_rcvmsg(struct msgb *msg, uint8_t link_id)
* MSC */
dispatch_dtap(lchan->conn, link_id, msg);
} else {
- /* fwd via bsc_api to send COMPLETE L3 INFO to MSC */
return bsc_compl_l3(lchan, msg, 0);
}
diff --git a/src/osmo-bsc/gsm_08_08.c b/src/osmo-bsc/gsm_08_08.c
index b7c744865..cd8b77f79 100644
--- a/src/osmo-bsc/gsm_08_08.c
+++ b/src/osmo-bsc/gsm_08_08.c
@@ -30,6 +30,9 @@
#include <osmocom/bsc/gsm_04_08_rr.h>
#include <osmocom/bsc/a_reset.h>
+#include <osmocom/bsc/lcs_ta_req.h>
+#include <osmocom/bsc/lcs_loc_req.h>
+
#include <osmocom/gsm/protocol/gsm_08_08.h>
#include <osmocom/gsm/gsm0808.h>
#include <osmocom/gsm/mncc.h>
@@ -495,6 +498,10 @@ int bsc_compl_l3(struct gsm_lchan *lchan, struct msgb *msg, uint16_t chosen_chan
parse_powercap(conn, msg);
+ /* If a BSSLAP TA Request from the SMLC is waiting for a TA value, we have one now. */
+ if (conn->lcs.loc_req && conn->lcs.loc_req->ta_req)
+ osmo_fsm_inst_dispatch(conn->lcs.loc_req->ta_req->fi, LCS_TA_REQ_EV_GOT_TA, NULL);
+
/* If the Paging was issued only by OsmoBSC for LCS, don't bother to establish Layer 3 to the MSC. */
if (paged_from_msc && !(paging_reasons & BSC_PAGING_FROM_CN)) {
LOG_COMPL_L3(pdisc, mtype, LOGL_DEBUG,
diff --git a/src/osmo-bsc/gsm_data.c b/src/osmo-bsc/gsm_data.c
index fbc2ae213..1152783ae 100644
--- a/src/osmo-bsc/gsm_data.c
+++ b/src/osmo-bsc/gsm_data.c
@@ -45,6 +45,7 @@
#include <osmocom/bsc/timeslot_fsm.h>
#include <osmocom/bsc/lchan_fsm.h>
#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/bsc_msc_data.h>
void *tall_bsc_ctx = NULL;
diff --git a/src/osmo-bsc/handover_fsm.c b/src/osmo-bsc/handover_fsm.c
index 8e231e0eb..573f249a1 100644
--- a/src/osmo-bsc/handover_fsm.c
+++ b/src/osmo-bsc/handover_fsm.c
@@ -44,6 +44,7 @@
#include <osmocom/bsc/codec_pref.h>
#include <osmocom/bsc/gsm_08_08.h>
#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/lcs_loc_req.h>
#define LOG_FMT_BTS "bts %u lac-ci %u-%u arfcn-bsic %d-%d"
#define LOG_ARGS_BTS(bts) \
@@ -938,6 +939,10 @@ void handover_end(struct gsm_subscriber_connection *conn, enum handover_result r
if (ho->new_lchan && result == HO_RESULT_OK) {
gscon_change_primary_lchan(conn, conn->ho.new_lchan);
ho->new_lchan = NULL;
+
+ /* If a Perform Location Request (LCS) is busy, inform the SMLC that there is a new lchan */
+ if (conn->lcs.loc_req)
+ osmo_fsm_inst_dispatch(conn->lcs.loc_req->fi, LCS_LOC_REQ_EV_HANDOVER_PERFORMED, NULL);
}
osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_HANDOVER_END, &result);
diff --git a/src/osmo-bsc/lb.c b/src/osmo-bsc/lb.c
new file mode 100644
index 000000000..6ab131f66
--- /dev/null
+++ b/src/osmo-bsc/lb.c
@@ -0,0 +1,662 @@
+/* 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>
+
+static struct gsm_subscriber_connection *get_bsc_conn_by_lb_conn_id(int conn_id)
+{
+ struct gsm_subscriber_connection *conn;
+
+ llist_for_each_entry(conn, &bsc_gsmnet->subscr_conns, entry) {
+ if (conn->lcs.lb.state != SUBSCR_SCCP_ST_NONE
+ && conn->lcs.lb.conn_id == conn_id)
+ return conn;
+ }
+
+ return NULL;
+}
+
+/* Send reset to SMLC */
+int bssmap_le_tx_reset()
+ // TODO use this -- patch coming up
+{
+ 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,
+ },
+ };
+
+ ss7 = osmo_ss7_instance_find(bsc_gsmnet->smlc->cs7_instance);
+ OSMO_ASSERT(ss7);
+ LOGP(DLCS, LOGL_NOTICE, "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(&bsc_gsmnet->smlc->ctrs->ctr[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()
+{
+ 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(DLCS, LOGL_NOTICE, "Tx 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(&bsc_gsmnet->smlc->ctrs->ctr[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 bssmap_le_handle_reset(const struct bssmap_le_pdu *pdu)
+{
+ struct gsm_subscriber_connection *conn;
+ int rc;
+
+ /* Abort all ongoing Location Requests */
+ llist_for_each_entry(conn, &bsc_gsmnet->subscr_conns, entry)
+ lcs_loc_req_reset(conn);
+
+ rc = bssmap_le_tx_reset_ack();
+ if (!rc)
+ bsc_gsmnet->smlc->ready = true;
+ return rc;
+}
+
+static int bssmap_le_handle_reset_ack()
+{
+ bsc_gsmnet->smlc->ready = true;
+ return 0;
+}
+
+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;
+ struct rate_ctr *ctr = bsc_gsmnet->smlc->ctrs->ctr;
+
+ 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(&ctr[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(&ctr[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(&ctr[SMLC_CTR_BSSMAP_LE_RX_UDT_RESET]);
+ LOGP(DLCS, LOGL_NOTICE, "RESET from SMLC: %s\n", osmo_sccp_addr_name(ss7, smlc_addr));
+ return bssmap_le_handle_reset(&bssap_le.bssmap_le);
+ case BSSMAP_LE_MSGT_RESET_ACK:
+ rate_ctr_inc(&ctr[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 bssmap_le_handle_reset_ack();
+ default:
+ rate_ctr_inc(&ctr[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 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 = get_bsc_conn_by_lb_conn_id(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 = get_bsc_conn_by_lb_conn_id(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 = get_bsc_conn_by_lb_conn_id(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;
+ int conn_id;
+ int rc;
+
+ 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_gsmnet->smlc->sccp);
+ if (conn_id < 0) {
+ 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=%i) 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(&bsc_gsmnet->smlc->ctrs->ctr[SMLC_CTR_BSSMAP_LE_TX_SUCCESS]);
+ else
+ rate_ctr_inc(&bsc_gsmnet->smlc->ctrs->ctr[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);
+
+ 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(&bsc_gsmnet->smlc->ctrs->ctr[SMLC_CTR_BSSMAP_LE_TX_SUCCESS]);
+ else
+ rate_ctr_inc(&bsc_gsmnet->smlc->ctrs->ctr[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(&bsc_gsmnet->smlc->ctrs->ctr[SMLC_CTR_BSSMAP_LE_TX_DT1_PERFORM_LOCATION_REQUEST]);
+ break;
+ case BSSMAP_LE_MSGT_PERFORM_LOC_ABORT:
+ rate_ctr_inc(&bsc_gsmnet->smlc->ctrs->ctr[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(&bsc_gsmnet->smlc->ctrs->ctr[SMLC_CTR_BSSMAP_LE_TX_DT1_BSSLAP_TA_RESPONSE]);
+ break;
+ case BSSLAP_MSGT_REJECT:
+ rate_ctr_inc(&bsc_gsmnet->smlc->ctrs->ctr[SMLC_CTR_BSSMAP_LE_TX_DT1_BSSLAP_REJECT]);
+ break;
+ case BSSLAP_MSGT_RESET:
+ rate_ctr_inc(&bsc_gsmnet->smlc->ctrs->ctr[SMLC_CTR_BSSMAP_LE_TX_DT1_BSSLAP_RESET]);
+ break;
+ case BSSLAP_MSGT_ABORT:
+ rate_ctr_inc(&bsc_gsmnet->smlc->ctrs->ctr[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"
+
+/* Initialize Lb interface to SMLC */
+int lb_init()
+{
+ 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";
+
+ if (!bsc_gsmnet->smlc) {
+ bsc_gsmnet->smlc = talloc_zero(bsc_gsmnet, struct smlc_config);
+ bsc_gsmnet->smlc->ctrs = rate_ctr_group_alloc(bsc_gsmnet, &smlc_ctrg_desc, 0);
+ }
+ OSMO_ASSERT(bsc_gsmnet->smlc);
+
+ 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;
+
+ /* 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));
+
+ /* Bind SCCP user. */
+ bsc_gsmnet->smlc->sccp_user = osmo_sccp_user_find(sccp, bsc_gsmnet->smlc->bsc_addr.ssn, bsc_gsmnet->smlc->bsc_addr.pc);
+ LOGP(DLCS, LOGL_NOTICE, "%s %s: %s\n", inst_name, smlc_name,
+ bsc_gsmnet->smlc->sccp_user ? "user already bound for this SCCP instance" : "binding SCCP user");
+ if (!bsc_gsmnet->smlc->sccp_user)
+ 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;
+
+ 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,
+};
+
+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;
+}
+
+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;
+ }
+
+ /* Prevent mixing addresses from different CS7 instances */
+ if (bsc_gsmnet->smlc->cs7_instance_valid
+ && bsc_gsmnet->smlc->cs7_instance != ss7->cfg.id) {
+ vty_out(vty,
+ "Error: SCCP addressbook entry from mismatching CS7 instance: '%s'%s",
+ bsc_addr_name, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bsc_gsmnet->smlc->cs7_instance = ss7->cfg.id;
+ bsc_gsmnet->smlc->cs7_instance_valid = true;
+ 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;
+ }
+
+ /* Prevent mixing addresses from different CS7/SS7 instances */
+ if (bsc_gsmnet->smlc->cs7_instance_valid) {
+ if (bsc_gsmnet->smlc->cs7_instance != ss7->cfg.id) {
+ vty_out(vty,
+ "Error: SCCP addressbook entry from mismatching CS7 instance: '%s'%s",
+ smlc_addr_name, VTY_NEWLINE);
+ return CMD_ERR_INCOMPLETE;
+ }
+ }
+
+ bsc_gsmnet->smlc->cs7_instance = ss7->cfg.id;
+ bsc_gsmnet->smlc->cs7_instance_valid = true;
+ 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)
+{
+ vty_out(vty, "smlc%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_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,
+};
diff --git a/src/osmo-bsc/lcs_loc_req.c b/src/osmo-bsc/lcs_loc_req.c
new file mode 100644
index 000000000..ca5c7b93f
--- /dev/null
+++ b/src/osmo-bsc/lcs_loc_req.c
@@ -0,0 +1,581 @@
+/* Handle LCS BSSMAP-LE Perform Location Request */
+/*
+ * (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/lcs_loc_req.h>
+
+#include <osmocom/bsc/bsc_msc_data.h>
+#include <osmocom/bsc/bsc_subscr_conn_fsm.h>
+#include <osmocom/bsc/bsc_subscriber.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/lb.h>
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/tdef.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/gad.h>
+#include <osmocom/gsm/bsslap.h>
+#include <osmocom/gsm/bssmap_le.h>
+#include <osmocom/gsm/gsm0808_lcs.h>
+#include <osmocom/bsc/lcs_ta_req.h>
+#include <osmocom/bsc/paging.h>
+#include <osmocom/bsc/bts_trx.h>
+#include <osmocom/bsc/bts.h>
+
+enum lcs_loc_req_fsm_state {
+ LCS_LOC_REQ_ST_INIT,
+ LCS_LOC_REQ_ST_WAIT_LOCATION_RESPONSE,
+ LCS_LOC_REQ_ST_BSSLAP_TA_REQ_ONGOING,
+ LCS_LOC_REQ_ST_GOT_LOCATION_RESPONSE,
+ LCS_LOC_REQ_ST_FAILED,
+};
+
+static const struct value_string lcs_loc_req_fsm_event_names[] = {
+ OSMO_VALUE_STRING(LCS_LOC_REQ_EV_RX_LB_PERFORM_LOCATION_RESPONSE),
+ OSMO_VALUE_STRING(LCS_LOC_REQ_EV_RX_A_PERFORM_LOCATION_ABORT),
+ OSMO_VALUE_STRING(LCS_LOC_REQ_EV_TA_REQ_START),
+ OSMO_VALUE_STRING(LCS_LOC_REQ_EV_TA_REQ_END),
+ OSMO_VALUE_STRING(LCS_LOC_REQ_EV_HANDOVER_PERFORMED),
+ OSMO_VALUE_STRING(LCS_LOC_REQ_EV_CONN_CLEAR),
+ {}
+};
+
+static struct osmo_fsm lcs_loc_req_fsm;
+
+static const struct osmo_tdef_state_timeout lcs_loc_req_fsm_timeouts[32] = {
+ [LCS_LOC_REQ_ST_WAIT_LOCATION_RESPONSE] = { .T = -11 },
+};
+
+/* Transition to a state, using the T timer defined in lcs_loc_req_fsm_timeouts.
+ * The actual timeout value is in turn obtained from network->T_defs.
+ * Assumes local variable fi exists. */
+#define lcs_loc_req_fsm_state_chg(FI, STATE) \
+ osmo_tdef_fsm_inst_state_chg(FI, STATE, \
+ lcs_loc_req_fsm_timeouts, \
+ (bsc_gsmnet)->T_defs, \
+ 5)
+
+#define lcs_loc_req_fail(cause, fmt, args...) do { \
+ LOG_LCS_LOC_REQ(lcs_loc_req, LOGL_ERROR, "Perform Location Request failed in state %s: " fmt "\n", \
+ lcs_loc_req ? osmo_fsm_inst_state_name(lcs_loc_req->fi) : "NULL", ## args); \
+ lcs_loc_req->lcs_cause = (struct lcs_cause_ie){ \
+ .present = true, \
+ .cause_val = cause, \
+ }; \
+ lcs_loc_req_fsm_state_chg(lcs_loc_req->fi, LCS_LOC_REQ_ST_FAILED); \
+ } while(0)
+
+static struct lcs_loc_req *lcs_loc_req_alloc(struct osmo_fsm_inst *parent_fi, uint32_t parent_event_term)
+{
+ struct lcs_loc_req *lcs_loc_req;
+
+ struct osmo_fsm_inst *fi = osmo_fsm_inst_alloc_child(&lcs_loc_req_fsm, parent_fi, parent_event_term);
+ OSMO_ASSERT(fi);
+
+ lcs_loc_req = talloc(fi, struct lcs_loc_req);
+ OSMO_ASSERT(lcs_loc_req);
+ fi->priv = lcs_loc_req;
+ *lcs_loc_req = (struct lcs_loc_req){
+ .fi = fi,
+ };
+
+ return lcs_loc_req;
+}
+
+static bool parse_bssmap_perf_loc_req(struct lcs_loc_req *lcs_loc_req, struct msgb *msg)
+{
+ struct tlv_parsed tp_arr[1];
+ struct tlv_parsed *tp = &tp_arr[0];
+ struct tlv_p_entry *e;
+ int payload_length;
+
+#define PARSE_ERR(ERRMSG) do { \
+ lcs_loc_req_fail(LCS_CAUSE_PROTOCOL_ERROR, "rx BSSMAP Perform Location Request: " ERRMSG); \
+ return false; \
+ } while(0)
+
+ payload_length = msg->tail - msg->l4h;
+ if (tlv_parse2(tp_arr, 1, gsm0808_att_tlvdef(), msg->l4h + 1, payload_length - 1, 0, 0) <= 0)
+ PARSE_ERR("Failed to parse IEs");
+
+ if (!(e = TLVP_GET(tp, GSM0808_IE_LOCATION_TYPE)))
+ PARSE_ERR("Missing Location Type IE");
+ if (osmo_bssmap_le_ie_dec_location_type(&lcs_loc_req->req.location_type, -1, -1, NULL, NULL, e->val, e->len))
+ PARSE_ERR("Failed to parse Location Type IE");
+
+ if ((e = TLVP_GET(tp, GSM0808_IE_CELL_IDENTIFIER))) {
+ if (gsm0808_dec_cell_id(&lcs_loc_req->req.cell_id, e->val, e->len) <= 0)
+ PARSE_ERR("Failed to parse Cell Identifier IE");
+ lcs_loc_req->req.cell_id_present = true;
+ }
+
+ if ((e = TLVP_GET(tp, GSM0808_IE_IMSI))) {
+ if (osmo_mobile_identity_decode(&lcs_loc_req->req.imsi, e->val, e->len, false)
+ || lcs_loc_req->req.imsi.type != GSM_MI_TYPE_IMSI)
+ PARSE_ERR("Failed to parse IMSI IE");
+ }
+
+ if ((e = TLVP_GET(tp, GSM0808_IE_IMEI))) {
+ if (osmo_mobile_identity_decode(&lcs_loc_req->req.imei, e->val, e->len, false)
+ || lcs_loc_req->req.imei.type != GSM_MI_TYPE_IMEI)
+ PARSE_ERR("Failed to parse IMEI IE");
+ }
+
+ // FIXME LCS QoS IE is mandatory for requesting the location
+
+ /* A lot of IEs remain ignored... */
+
+ return true;
+#undef PARSE_ERR
+}
+
+void lcs_loc_req_start(struct gsm_subscriber_connection *conn, struct msgb *loc_req_msg)
+{
+ struct lcs_loc_req *lcs_loc_req;
+
+ if (conn->lcs.loc_req) {
+ LOG_LCS_LOC_REQ(conn, LOGL_ERROR,
+ "Ignoring Perform Location Request, another request is still pending\n");
+ return;
+ }
+
+ lcs_loc_req = lcs_loc_req_alloc(conn->fi, GSCON_EV_LCS_LOC_REQ_END);
+
+ lcs_loc_req->conn = conn;
+ conn->lcs.loc_req = lcs_loc_req;
+
+ if (!parse_bssmap_perf_loc_req(lcs_loc_req, loc_req_msg))
+ return;
+
+ if (!conn->bsub) {
+ if (lcs_loc_req->req.imsi.type != GSM_MI_TYPE_IMSI) {
+ lcs_loc_req_fail(LCS_CAUSE_DATA_MISSING_IN_REQ,
+ "tx Perform Location Request: Missing identity:"
+ " No IMSI included in request, and also no active subscriber");
+ return;
+ }
+
+ conn->bsub = bsc_subscr_find_or_create_by_mi(bsc_gsmnet->bsc_subscribers, &lcs_loc_req->req.imsi,
+ BSUB_USE_CONN);
+ if (!conn->bsub) {
+ lcs_loc_req_fail(LCS_CAUSE_SYSTEM_FAILURE,
+ "tx Perform Location Request: Cannot assign subscriber");
+ return;
+ }
+ }
+
+ /* state change to start the timeout */
+ lcs_loc_req_fsm_state_chg(lcs_loc_req->fi, LCS_LOC_REQ_ST_WAIT_LOCATION_RESPONSE);
+}
+
+static int handle_bssmap_le_conn_oriented_info(struct lcs_loc_req *lcs_loc_req, const struct bssmap_le_pdu *bssmap_le)
+{
+ switch (bssmap_le->conn_oriented_info.apdu.msg_type) {
+ case BSSLAP_MSGT_TA_REQUEST:
+ rate_ctr_inc(&bsc_gsmnet->smlc->ctrs->ctr[SMLC_CTR_BSSMAP_LE_RX_DT1_BSSLAP_TA_REQUEST]);
+ LOG_LCS_LOC_REQ(lcs_loc_req, LOGL_DEBUG, "rx BSSLAP TA Request\n");
+ /* The TA Request message contains only the message type. */
+ return lcs_ta_req_start(lcs_loc_req);
+ default:
+ LOG_LCS_LOC_REQ(lcs_loc_req, LOGL_ERROR, "rx BSSLAP APDU with unsupported message type %d\n",
+ bssmap_le->conn_oriented_info.apdu.msg_type);
+ return -ENOTSUP;
+ };
+}
+
+int lcs_loc_req_rx_bssmap_le(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+ struct lcs_loc_req *lcs_loc_req = conn->lcs.loc_req;
+ struct bssap_le_pdu bssap_le;
+ struct osmo_bssap_le_err *err;
+ struct rate_ctr *ctr = bsc_gsmnet->smlc->ctrs->ctr;
+
+ if (!lcs_loc_req) {
+ LOGPFSMSL(conn->fi, DLCS, LOGL_ERROR,
+ "Rx BSSMAP-LE message, but no Location Request is ongoing\n");
+ return -EINVAL;
+ }
+
+ if (osmo_bssap_le_dec(&bssap_le, &err, msg, msg)) {
+ LOG_LCS_LOC_REQ(lcs_loc_req, LOGL_ERROR, "Rx BSSAP-LE message with error: %s\n", err->logmsg);
+ rate_ctr_inc(&ctr[SMLC_CTR_BSSMAP_LE_RX_DT1_ERR_INVALID_MSG]);
+ return -EINVAL;
+ }
+
+ if (bssap_le.discr != BSSAP_LE_MSG_DISCR_BSSMAP_LE) {
+ LOG_LCS_LOC_REQ(lcs_loc_req, LOGL_ERROR, "Rx BSSAP-LE: discr %d not implemented\n", bssap_le.discr);
+ rate_ctr_inc(&ctr[SMLC_CTR_BSSMAP_LE_RX_DT1_ERR_INVALID_MSG]);
+ return -ENOTSUP;
+ }
+
+ LOG_LCS_LOC_REQ(lcs_loc_req, LOGL_DEBUG, "Rx %s\n", osmo_bssap_le_pdu_to_str_c(OTC_SELECT, &bssap_le));
+
+ switch (bssap_le.bssmap_le.msg_type) {
+ case BSSMAP_LE_MSGT_PERFORM_LOC_RESP:
+ if (bssap_le.bssmap_le.perform_loc_resp.location_estimate_present)
+ rate_ctr_inc(&ctr[SMLC_CTR_BSSMAP_LE_RX_DT1_PERFORM_LOCATION_RESPONSE_SUCCESS]);
+ else
+ rate_ctr_inc(&ctr[SMLC_CTR_BSSMAP_LE_RX_DT1_PERFORM_LOCATION_RESPONSE_FAILURE]);
+ return osmo_fsm_inst_dispatch(lcs_loc_req->fi, LCS_LOC_REQ_EV_RX_LB_PERFORM_LOCATION_RESPONSE,
+ &bssap_le.bssmap_le);
+
+ case BSSMAP_LE_MSGT_CONN_ORIENTED_INFO:
+ return handle_bssmap_le_conn_oriented_info(lcs_loc_req, &bssap_le.bssmap_le);
+
+ default:
+ LOG_LCS_LOC_REQ(lcs_loc_req, LOGL_ERROR, "Rx BSSMAP-LE from SMLC with unsupported message type: %s\n",
+ osmo_bssap_le_pdu_to_str_c(OTC_SELECT, &bssap_le));
+ return -ENOTSUP;
+ }
+}
+
+void lcs_loc_req_reset(struct gsm_subscriber_connection *conn)
+{
+ struct lcs_loc_req *lcs_loc_req = conn->lcs.loc_req;
+ if (!lcs_loc_req)
+ return;
+ lcs_loc_req_fail(LCS_CAUSE_SYSTEM_FAILURE, "Aborting Location Request due to RESET on Lb");
+}
+
+static int lcs_loc_req_fsm_timer_cb(struct osmo_fsm_inst *fi)
+{
+ struct lcs_loc_req *lcs_loc_req = fi->priv;
+ lcs_loc_req_fail(LCS_CAUSE_SYSTEM_FAILURE, "Timeout");
+ return 1;
+}
+
+static int lcs_loc_req_send(struct lcs_loc_req *lcs_loc_req, const struct bssap_le_pdu *bssap_le)
+{
+ int rc = lb_send(lcs_loc_req->conn, bssap_le);
+ if (rc)
+ lcs_loc_req_fail(LCS_CAUSE_SYSTEM_FAILURE,
+ "Failed to send %s", osmo_bssap_le_pdu_to_str_c(OTC_SELECT, bssap_le));
+ return rc;
+}
+
+static void lcs_loc_req_wait_loc_resp_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct lcs_loc_req *lcs_loc_req = fi->priv;
+ struct bssap_le_pdu plr;
+ struct gsm_lchan *lchan;
+
+ if (prev_state == LCS_LOC_REQ_ST_BSSLAP_TA_REQ_ONGOING) {
+ /* LCS_LOC_REQ_ST_BSSLAP_TA_REQ_ONGOING should halt the FSM timeout. As soon as the TA Request is
+ * served, re-entering LCS_LOC_REQ_ST_WAIT_LOCATION_RESPONSE, but of course there is then no need to
+ * send a second BSSMAP-LE Perform Location Request to the SMLC. */
+ return;
+ }
+
+ if (!lcs_loc_req->req.cell_id_present) {
+ lcs_loc_req_fail(LCS_CAUSE_PROTOCOL_ERROR,
+ "Cannot encode BSSMAP-LE Perform Location Request,"
+ " because mandatory Cell Identity is not known");
+ return;
+ }
+
+ plr = (struct bssap_le_pdu){
+ .discr = BSSAP_LE_MSG_DISCR_BSSMAP_LE,
+ .bssmap_le = {
+ .msg_type = BSSMAP_LE_MSGT_PERFORM_LOC_REQ,
+ .perform_loc_req = {
+ .location_type = lcs_loc_req->req.location_type,
+ .cell_id = lcs_loc_req->req.cell_id,
+ .imsi = lcs_loc_req->req.imsi,
+ .imei = lcs_loc_req->req.imei,
+ },
+ },
+ };
+
+ /* If we already have an active lchan, send the known TA directly to the SMLC */
+ lchan = lcs_loc_req->conn->lchan;
+ if (lchan) {
+ LOG_LCS_LOC_REQ(lcs_loc_req, LOGL_DEBUG,
+ "Active lchan present, including BSSLAP APDU with TA Layer 3\n");
+ plr.bssmap_le.perform_loc_req.apdu_present = true;
+ plr.bssmap_le.perform_loc_req.apdu = (struct bsslap_pdu){
+ .msg_type = BSSLAP_MSGT_TA_LAYER3,
+ .ta_layer3 = {
+ .ta = lchan->rqd_ta,
+ },
+ };
+ } else {
+ LOG_LCS_LOC_REQ(lcs_loc_req, LOGL_DEBUG,
+ "No active lchan, not including BSSLAP APDU\n");
+ }
+
+ /* Establish Lb connection to SMLC and send the BSSMAP-LE Perform Location Request */
+ lcs_loc_req_send(lcs_loc_req, &plr);
+}
+
+static void lcs_loc_req_bssmap_le_abort(struct lcs_loc_req *lcs_loc_req)
+{
+ struct bssap_le_pdu pla = {
+ .discr = BSSAP_LE_MSG_DISCR_BSSMAP_LE,
+ .bssmap_le = {
+ .msg_type = BSSMAP_LE_MSGT_PERFORM_LOC_ABORT,
+ .perform_loc_abort = {
+ .present = true,
+ .cause_val = LCS_CAUSE_REQUEST_ABORTED,
+ },
+ },
+ };
+
+ lcs_loc_req_send(lcs_loc_req, &pla);
+}
+
+/* After a handover, send the new lchan information to the SMLC via a BSSLAP Reset message.
+ * See 3GPP TS 48.071 4.2.6 Reset. */
+static void lcs_loc_req_handover_performed(struct lcs_loc_req *lcs_loc_req)
+{
+ struct gsm_lchan *lchan = lcs_loc_req->conn->lchan;
+ struct bssap_le_pdu bsslap = {
+ .discr = BSSAP_LE_MSG_DISCR_BSSMAP_LE,
+ .bssmap_le = {
+ .msg_type = BSSMAP_LE_MSGT_CONN_ORIENTED_INFO,
+ },
+ };
+ struct bsslap_pdu *apdu = &bsslap.bssmap_le.conn_oriented_info.apdu;
+
+ if (!lchan) {
+ /* The handover was out of this BSS. Abort the location procedure. */
+ *apdu = (struct bsslap_pdu){
+ .msg_type = BSSLAP_MSGT_ABORT,
+ .abort = BSSLAP_CAUSE_INTER_BSS_HO,
+ };
+ } else {
+ *apdu = (struct bsslap_pdu){
+ .msg_type = BSSLAP_MSGT_RESET,
+ .reset = {
+ .cell_id = lchan->ts->trx->bts->cell_identity,
+ .ta = lchan->rqd_ta,
+ .cause = BSSLAP_CAUSE_INTRA_BSS_HO,
+ },
+ };
+ gsm48_lchan2chan_desc(&apdu->reset.chan_desc, lchan);
+ }
+
+ lcs_loc_req_send(lcs_loc_req, &bsslap);
+}
+
+static void lcs_loc_req_wait_loc_resp_and_ta_req_ongoing_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct lcs_loc_req *lcs_loc_req = fi->priv;
+ const struct bssmap_le_pdu *bssmap_le;
+
+ switch (event) {
+
+ case LCS_LOC_REQ_EV_RX_LB_PERFORM_LOCATION_RESPONSE:
+ bssmap_le = data;
+ OSMO_ASSERT(bssmap_le->msg_type == BSSMAP_LE_MSGT_PERFORM_LOC_RESP);
+ lcs_loc_req->resp = bssmap_le->perform_loc_resp;
+ lcs_loc_req->resp_present = true;
+ lcs_loc_req_fsm_state_chg(fi, LCS_LOC_REQ_ST_GOT_LOCATION_RESPONSE);
+ break;
+
+ case LCS_LOC_REQ_EV_TA_REQ_START:
+ if (fi->state != LCS_LOC_REQ_ST_BSSLAP_TA_REQ_ONGOING)
+ lcs_loc_req_fsm_state_chg(fi, LCS_LOC_REQ_ST_BSSLAP_TA_REQ_ONGOING);
+ break;
+
+ case LCS_LOC_REQ_EV_TA_REQ_END:
+ if (fi->state != LCS_LOC_REQ_ST_WAIT_LOCATION_RESPONSE)
+ lcs_loc_req_fsm_state_chg(fi, LCS_LOC_REQ_ST_WAIT_LOCATION_RESPONSE);
+ break;
+
+ case LCS_LOC_REQ_EV_HANDOVER_PERFORMED:
+ lcs_loc_req_handover_performed(lcs_loc_req);
+ break;
+
+ case LCS_LOC_REQ_EV_RX_A_PERFORM_LOCATION_ABORT:
+ case LCS_LOC_REQ_EV_CONN_CLEAR:
+ if (lcs_loc_req->ta_req)
+ osmo_fsm_inst_dispatch(lcs_loc_req->ta_req->fi, LCS_TA_REQ_EV_ABORT, NULL);
+ lcs_loc_req_bssmap_le_abort(lcs_loc_req);
+ osmo_fsm_inst_term(lcs_loc_req->fi, OSMO_FSM_TERM_REGULAR, NULL);
+ break;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void lcs_loc_req_got_loc_resp_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct lcs_loc_req *lcs_loc_req = fi->priv;
+ struct msgb *msg;
+ int rc;
+ struct gsm0808_perform_location_response plr = {
+ .location_estimate_present = lcs_loc_req->resp.location_estimate_present,
+ .location_estimate = lcs_loc_req->resp.location_estimate,
+ .lcs_cause = lcs_loc_req->resp.lcs_cause,
+ };
+
+ if (plr.location_estimate_present) {
+ struct osmo_gad gad;
+ struct osmo_gad_err *err;
+ if (osmo_gad_dec(&gad, &err, OTC_SELECT, &plr.location_estimate))
+ LOG_LCS_LOC_REQ(lcs_loc_req, LOGL_ERROR,
+ "Perform Location Response contains Location Estimate with error: %s\n",
+ err->logmsg);
+ else
+ LOG_LCS_LOC_REQ(lcs_loc_req, LOGL_INFO,
+ "Perform Location Response contains Location Estimate: %s\n",
+ osmo_gad_to_str_c(OTC_SELECT, &gad));
+ }
+
+ if (plr.lcs_cause.present) {
+ LOG_LCS_LOC_REQ(lcs_loc_req, LOGL_ERROR,
+ "Perform Location Response contains error cause: %d\n",
+ plr.lcs_cause.cause_val);
+ }
+
+ msg = gsm0808_create_perform_location_response(&plr);
+ if (!msg) {
+ LOG_LCS_LOC_REQ(lcs_loc_req, LOGL_ERROR,
+ "Failed to encode BSSMAP Perform Location Response (A-interface)\n");
+ } else {
+ rc = gscon_sigtran_send(lcs_loc_req->conn, msg);
+ if (rc < 0)
+ LOG_LCS_LOC_REQ(lcs_loc_req, LOGL_ERROR,
+ "Failed to send Perform Location Response (A-interface)\n");
+ else
+ rate_ctr_inc(&lcs_loc_req->conn->sccp.msc->msc_ctrs->ctr[
+ plr.location_estimate_present ? MSC_CTR_BSSMAP_TX_DT1_PERFORM_LOCATION_RESPONSE_SUCCESS
+ : MSC_CTR_BSSMAP_TX_DT1_PERFORM_LOCATION_RESPONSE_FAILURE]);
+ }
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
+}
+
+static void lcs_loc_req_failed_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct lcs_loc_req *lcs_loc_req = fi->priv;
+ struct msgb *msg;
+ int rc;
+ struct bssap_le_pdu pla = {
+ .discr = BSSAP_LE_MSG_DISCR_BSSMAP_LE,
+ .bssmap_le = {
+ .msg_type = BSSMAP_LE_MSGT_PERFORM_LOC_ABORT,
+ .perform_loc_abort = lcs_loc_req->lcs_cause,
+ },
+ };
+ struct gsm0808_perform_location_response plr = {
+ .lcs_cause = lcs_loc_req->lcs_cause,
+ };
+
+ /* If we're paging this subscriber for LCS, stop paging. */
+ paging_request_cancel(lcs_loc_req->conn->bsub, BSC_PAGING_FOR_LCS);
+
+ /* Send Perform Location Abort to SMLC, only if we got started on the Lb */
+ if (lcs_loc_req->conn->lcs.lb.state == SUBSCR_SCCP_ST_CONNECTED)
+ lcs_loc_req_send(lcs_loc_req, &pla);
+
+ /* Send Perform Location Result with failure cause to MSC */
+ msg = gsm0808_create_perform_location_response(&plr);
+ if (!msg) {
+ LOG_LCS_LOC_REQ(lcs_loc_req, LOGL_ERROR,
+ "Failed to encode BSSMAP Perform Location Response (A-interface)\n");
+ } else {
+ rc = gscon_sigtran_send(lcs_loc_req->conn, msg);
+ if (rc < 0)
+ LOG_LCS_LOC_REQ(lcs_loc_req, LOGL_ERROR,
+ "Failed to send BSSMAP Perform Location Response (A-interface)\n");
+ else
+ rate_ctr_inc(&lcs_loc_req->conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_DT1_PERFORM_LOCATION_RESPONSE_FAILURE]);
+ }
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
+}
+
+void lcs_loc_req_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
+{
+ struct lcs_loc_req *lcs_loc_req = fi->priv;
+ if (lcs_loc_req->conn && lcs_loc_req->conn->lcs.loc_req == lcs_loc_req)
+ lcs_loc_req->conn->lcs.loc_req = NULL;
+ /* FSM termination will dispatch GSCON_EV_LCS_LOC_REQ_END to the conn FSM */
+}
+
+#define S(x) (1 << (x))
+
+static const struct osmo_fsm_state lcs_loc_req_fsm_states[] = {
+ [LCS_LOC_REQ_ST_INIT] = {
+ .name = "INIT",
+ .out_state_mask = 0
+ | S(LCS_LOC_REQ_ST_WAIT_LOCATION_RESPONSE)
+ | S(LCS_LOC_REQ_ST_FAILED)
+ ,
+ },
+ [LCS_LOC_REQ_ST_WAIT_LOCATION_RESPONSE] = {
+ .name = "WAIT_LOCATION_RESPONSE",
+ .in_event_mask = 0
+ | S(LCS_LOC_REQ_EV_RX_LB_PERFORM_LOCATION_RESPONSE)
+ | S(LCS_LOC_REQ_EV_RX_A_PERFORM_LOCATION_ABORT)
+ | S(LCS_LOC_REQ_EV_TA_REQ_START)
+ | S(LCS_LOC_REQ_EV_TA_REQ_END)
+ | S(LCS_LOC_REQ_EV_HANDOVER_PERFORMED)
+ | S(LCS_LOC_REQ_EV_CONN_CLEAR)
+ ,
+ .out_state_mask = 0
+ | S(LCS_LOC_REQ_ST_BSSLAP_TA_REQ_ONGOING)
+ | S(LCS_LOC_REQ_ST_GOT_LOCATION_RESPONSE)
+ | S(LCS_LOC_REQ_ST_FAILED)
+ ,
+ .onenter = lcs_loc_req_wait_loc_resp_onenter,
+ .action = lcs_loc_req_wait_loc_resp_and_ta_req_ongoing_action,
+ },
+ [LCS_LOC_REQ_ST_BSSLAP_TA_REQ_ONGOING] = {
+ .name = "BSSLAP_TA_REQ_ONGOING",
+ .in_event_mask = 0
+ | S(LCS_LOC_REQ_EV_RX_LB_PERFORM_LOCATION_RESPONSE)
+ | S(LCS_LOC_REQ_EV_RX_A_PERFORM_LOCATION_ABORT)
+ | S(LCS_LOC_REQ_EV_TA_REQ_END)
+ | S(LCS_LOC_REQ_EV_HANDOVER_PERFORMED)
+ | S(LCS_LOC_REQ_EV_CONN_CLEAR)
+ ,
+ .out_state_mask = 0
+ | S(LCS_LOC_REQ_ST_WAIT_LOCATION_RESPONSE)
+ | S(LCS_LOC_REQ_ST_GOT_LOCATION_RESPONSE)
+ | S(LCS_LOC_REQ_ST_FAILED)
+ ,
+ .action = lcs_loc_req_wait_loc_resp_and_ta_req_ongoing_action,
+ },
+ [LCS_LOC_REQ_ST_GOT_LOCATION_RESPONSE] = {
+ .name = "GOT_LOCATION_RESPONSE",
+ .onenter = lcs_loc_req_got_loc_resp_onenter,
+ },
+ [LCS_LOC_REQ_ST_FAILED] = {
+ .name = "FAILED",
+ .onenter = lcs_loc_req_failed_onenter,
+ },
+};
+
+static struct osmo_fsm lcs_loc_req_fsm = {
+ .name = "lcs_loc_req",
+ .states = lcs_loc_req_fsm_states,
+ .num_states = ARRAY_SIZE(lcs_loc_req_fsm_states),
+ .log_subsys = DLCS,
+ .event_names = lcs_loc_req_fsm_event_names,
+ .timer_cb = lcs_loc_req_fsm_timer_cb,
+ .cleanup = lcs_loc_req_fsm_cleanup,
+};
+
+static __attribute__((constructor)) void lcs_loc_req_fsm_register(void)
+{
+ OSMO_ASSERT(osmo_fsm_register(&lcs_loc_req_fsm) == 0);
+}
diff --git a/src/osmo-bsc/lcs_ta_req.c b/src/osmo-bsc/lcs_ta_req.c
new file mode 100644
index 000000000..97d6eb5c8
--- /dev/null
+++ b/src/osmo-bsc/lcs_ta_req.c
@@ -0,0 +1,305 @@
+/* Handle LCS BSSLAP TA Request */
+/*
+ * (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/lcs_ta_req.h>
+
+#include <osmocom/bsc/lcs_loc_req.h>
+#include <osmocom/bsc/lb.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/paging.h>
+#include <osmocom/bsc/bts.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/tdef.h>
+#include <osmocom/gsm/bsslap.h>
+
+enum lcs_ta_req_fsm_state {
+ LCS_TA_REQ_ST_INIT,
+ LCS_TA_REQ_ST_WAIT_TA,
+ LCS_TA_REQ_ST_GOT_TA,
+ LCS_TA_REQ_ST_FAILED,
+};
+
+static const struct value_string lcs_ta_req_fsm_event_names[] = {
+ OSMO_VALUE_STRING(LCS_TA_REQ_EV_GOT_TA),
+ OSMO_VALUE_STRING(LCS_TA_REQ_EV_ABORT),
+ {}
+};
+
+static const struct osmo_tdef_state_timeout lcs_ta_req_fsm_timeouts[32] = {
+ [LCS_TA_REQ_ST_WAIT_TA] = { .T = -12 },
+};
+
+/* Transition to a state, using the T timer defined in lcs_ta_req_fsm_timeouts.
+ * The actual timeout value is in turn obtained from network->T_defs.
+ * Assumes local variable fi exists. */
+#define lcs_ta_req_fsm_state_chg(FI, STATE) \
+ osmo_tdef_fsm_inst_state_chg(FI, STATE, \
+ lcs_ta_req_fsm_timeouts, \
+ (bsc_gsmnet)->T_defs, \
+ 5)
+
+#define lcs_ta_req_fail(cause, fmt, args...) do { \
+ LOG_LCS_TA_REQ(lcs_ta_req, LOGL_ERROR, "BSSLAP TA Request failed in state %s: " fmt "\n", \
+ lcs_ta_req ? osmo_fsm_inst_state_name(lcs_ta_req->fi) : "NULL", ## args); \
+ lcs_ta_req->failure_cause = cause; \
+ lcs_ta_req_fsm_state_chg(lcs_ta_req->fi, LCS_TA_REQ_ST_FAILED); \
+ } while(0)
+
+static struct osmo_fsm lcs_ta_req_fsm;
+
+static struct lcs_ta_req *lcs_ta_req_alloc(struct osmo_fsm_inst *parent_fi, uint32_t parent_event_term)
+{
+ struct lcs_ta_req *lcs_ta_req;
+
+ struct osmo_fsm_inst *fi = osmo_fsm_inst_alloc_child(&lcs_ta_req_fsm, parent_fi, parent_event_term);
+ OSMO_ASSERT(fi);
+
+ lcs_ta_req = talloc(fi, struct lcs_ta_req);
+ OSMO_ASSERT(lcs_ta_req);
+ fi->priv = lcs_ta_req;
+ *lcs_ta_req = (struct lcs_ta_req){
+ .fi = fi,
+ };
+
+ return lcs_ta_req;
+}
+
+int lcs_ta_req_start(struct lcs_loc_req *lcs_loc_req)
+{
+ struct lcs_ta_req *lcs_ta_req;
+ if (lcs_loc_req->ta_req) {
+ LOG_LCS_TA_REQ(lcs_loc_req->ta_req, LOGL_ERROR,
+ "Cannot start anoter TA Request FSM, this TA Request is still active\n");
+ return -ENOTSUP;
+ }
+ lcs_ta_req = lcs_ta_req_alloc(lcs_loc_req->fi, LCS_LOC_REQ_EV_TA_REQ_END);
+ if (!lcs_ta_req) {
+ LOG_LCS_LOC_REQ(lcs_loc_req, LOGL_ERROR, "Cannot allocate TA Request FSM");
+ return -ENOSPC;
+ }
+ lcs_ta_req->loc_req = lcs_loc_req;
+ lcs_loc_req->ta_req = lcs_ta_req;
+
+ return lcs_ta_req_fsm_state_chg(lcs_ta_req->fi, LCS_TA_REQ_ST_WAIT_TA);
+}
+
+static int lcs_ta_req_fsm_timer_cb(struct osmo_fsm_inst *fi)
+{
+ struct lcs_ta_req *lcs_ta_req = fi->priv;
+ lcs_ta_req_fail(LCS_CAUSE_SYSTEM_FAILURE, "Timeout");
+ return 1;
+}
+
+void lcs_ta_req_wait_ta_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct lcs_ta_req *lcs_ta_req = fi->priv;
+ struct lcs_loc_req *loc_req = lcs_ta_req->loc_req;
+ struct gsm_lchan *lchan;
+ struct bsc_paging_params paging;
+
+ if (osmo_fsm_inst_dispatch(loc_req->fi, LCS_LOC_REQ_EV_TA_REQ_START, lcs_ta_req)) {
+ lcs_ta_req_fail(LCS_CAUSE_SYSTEM_FAILURE, "Failed to dispatch LCS_LOC_REQ_EV_TA_REQ_START");
+ return;
+ }
+
+ paging = (struct bsc_paging_params){
+ .reason = BSC_PAGING_FOR_LCS,
+ .msc = loc_req->conn->sccp.msc,
+ .bsub = loc_req->conn->bsub,
+ .tmsi = GSM_RESERVED_TMSI,
+ .imsi = loc_req->req.imsi,
+ .chan_needed = RSL_CHANNEED_ANY,
+ };
+ if (paging.bsub)
+ bsc_subscr_get(paging.bsub, BSUB_USE_PAGING_START);
+
+ /* Do we already have an active lchan with knowledge of TA? */
+ lchan = loc_req->conn->lchan;
+ if (lchan) {
+ lcs_ta_req_fsm_state_chg(fi, LCS_TA_REQ_ST_GOT_TA);
+ return;
+ }
+
+ /* No lchan yet, need to start Paging */
+ if (loc_req->req.imsi.type != GSM_MI_TYPE_IMSI) {
+ lcs_ta_req_fail(LCS_CAUSE_PROTOCOL_ERROR,
+ "No IMSI in BSSMAP Location Request and no active lchan, cannot start Paging");
+ return;
+ }
+
+ if (!loc_req->req.cell_id_present) {
+ LOG_LCS_TA_REQ(lcs_ta_req, LOGL_DEBUG,
+ "No Cell Identity in BSSMAP Location Request, paging entire BSS\n");
+ paging.cil = (struct gsm0808_cell_id_list2){
+ .id_discr = CELL_IDENT_BSS,
+ };
+ } else {
+ paging.cil = (struct gsm0808_cell_id_list2){
+ .id_discr = loc_req->req.cell_id.id_discr,
+ .id_list = { loc_req->req.cell_id.id },
+ .id_list_len = 1,
+ };
+ }
+
+ bsc_paging_start(&paging);
+}
+
+static void lcs_ta_req_wait_ta_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+
+ case LCS_TA_REQ_EV_GOT_TA:
+ lcs_ta_req_fsm_state_chg(fi, LCS_TA_REQ_ST_GOT_TA);
+ break;
+
+ case LCS_TA_REQ_EV_ABORT:
+ lcs_ta_req_fsm_state_chg(fi, LCS_TA_REQ_ST_FAILED);
+ break;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static int lcs_ta_req_send(struct lcs_ta_req *lcs_ta_req, const struct bssap_le_pdu *bssap_le)
+{
+ int rc = lb_send(lcs_ta_req->loc_req->conn, bssap_le);
+ if (rc)
+ lcs_ta_req_fail(LCS_CAUSE_SYSTEM_FAILURE,
+ "Failed to send %s", osmo_bssap_le_pdu_to_str_c(OTC_SELECT, bssap_le));
+ return rc;
+}
+
+void lcs_ta_req_got_ta_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct lcs_ta_req *lcs_ta_req = fi->priv;
+ struct bssap_le_pdu bsslap_ta_resp;
+ struct gsm_lchan *lchan = lcs_ta_req->loc_req->conn->lchan;
+
+ if (!lchan) {
+ lcs_ta_req_fail(LCS_CAUSE_SYSTEM_FAILURE, "Internal error: no lchan");
+ return;
+ }
+
+ bsslap_ta_resp = (struct bssap_le_pdu) {
+ .discr = BSSAP_LE_MSG_DISCR_BSSMAP_LE,
+ .bssmap_le = {
+ .msg_type = BSSMAP_LE_MSGT_CONN_ORIENTED_INFO,
+ .conn_oriented_info = {
+ .apdu = {
+ .msg_type = BSSLAP_MSGT_TA_RESPONSE,
+ .ta_response = {
+ .cell_id = lchan->ts->trx->bts->cell_identity,
+ .ta = lchan->rqd_ta,
+ },
+ },
+ },
+ },
+ };
+
+ lcs_ta_req_send(lcs_ta_req, &bsslap_ta_resp);
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
+}
+
+void lcs_ta_req_failed_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct lcs_ta_req *lcs_ta_req = fi->priv;
+ struct bssap_le_pdu bsslap_abort;
+
+ bsslap_abort = (struct bssap_le_pdu) {
+ .discr = BSSAP_LE_MSG_DISCR_BSSMAP_LE,
+ .bssmap_le = {
+ .msg_type = BSSMAP_LE_MSGT_CONN_ORIENTED_INFO,
+ .conn_oriented_info = {
+ .apdu = {
+ .msg_type = BSSLAP_MSGT_ABORT,
+ .abort = BSSLAP_CAUSE_OTHER_RADIO_EVT_FAIL,
+ },
+ },
+ },
+ };
+
+ lcs_ta_req_send(lcs_ta_req, &bsslap_abort);
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
+}
+
+void lcs_ta_req_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
+{
+ struct lcs_ta_req *lcs_ta_req = fi->priv;
+ if (lcs_ta_req->loc_req->ta_req == lcs_ta_req)
+ lcs_ta_req->loc_req->ta_req = NULL;
+ /* FSM termination will dispatch LCS_LOC_REQ_EV_TA_REQ_END to the lcs_loc_req FSM */
+}
+
+
+#define S(x) (1 << (x))
+
+static const struct osmo_fsm_state lcs_ta_req_fsm_states[] = {
+ [LCS_TA_REQ_ST_INIT] = {
+ .name = "init",
+ .out_state_mask = 0
+ | S(LCS_TA_REQ_ST_WAIT_TA)
+ | S(LCS_TA_REQ_ST_GOT_TA)
+ ,
+ },
+ [LCS_TA_REQ_ST_WAIT_TA] = {
+ .name = "wait_ta",
+ .in_event_mask = 0
+ | S(LCS_TA_REQ_EV_GOT_TA)
+ | S(LCS_TA_REQ_EV_ABORT)
+ ,
+ .out_state_mask = 0
+ | S(LCS_TA_REQ_ST_GOT_TA)
+ | S(LCS_TA_REQ_ST_FAILED)
+ ,
+ .onenter = lcs_ta_req_wait_ta_onenter,
+ .action = lcs_ta_req_wait_ta_action,
+ },
+ [LCS_TA_REQ_ST_GOT_TA] = {
+ .name = "got_ta",
+ .in_event_mask = 0
+ ,
+ .out_state_mask = 0
+ ,
+ .onenter = lcs_ta_req_got_ta_onenter,
+ },
+ [LCS_TA_REQ_ST_FAILED] = {
+ .name = "failed",
+ .onenter = lcs_ta_req_failed_onenter,
+ },
+};
+
+static struct osmo_fsm lcs_ta_req_fsm = {
+ .name = "lcs_ta_req",
+ .states = lcs_ta_req_fsm_states,
+ .num_states = ARRAY_SIZE(lcs_ta_req_fsm_states),
+ .log_subsys = DLCS,
+ .event_names = lcs_ta_req_fsm_event_names,
+ .timer_cb = lcs_ta_req_fsm_timer_cb,
+ .cleanup = lcs_ta_req_fsm_cleanup,
+};
+
+static __attribute__((constructor)) void lcs_ta_req_fsm_register(void)
+{
+ OSMO_ASSERT(osmo_fsm_register(&lcs_ta_req_fsm) == 0);
+}
diff --git a/src/osmo-bsc/net_init.c b/src/osmo-bsc/net_init.c
index 8b1e080e2..6cbb40cdb 100644
--- a/src/osmo-bsc/net_init.c
+++ b/src/osmo-bsc/net_init.c
@@ -52,6 +52,7 @@ static struct osmo_tdef gsm_network_T_defs[] = {
{ .T=-8, .default_val=5, .desc="Timeout for RSL IPA MDCX ACK after sending RSL IPA MDCX" },
{ .T=-9, .default_val=5, .desc="Timeout for availability of MGW endpoint" },
{ .T=-10, .default_val=5, .desc="Timeout for fully configured MGW endpoint" },
+ { .T=-11, .default_val=5, .desc="Timeout for Perform Location Response from SMLC" },
{ .T=-3111, .default_val=4, .desc="Wait time after lchan was released in error (should be T3111 + 2s)" },
{ .T=-3210, .default_val=20, .desc="After L3 Complete, wait for MSC to confirm" },
{}
diff --git a/src/osmo-bsc/osmo_bsc_bssap.c b/src/osmo-bsc/osmo_bsc_bssap.c
index 10f0edd6d..c2c05affb 100644
--- a/src/osmo-bsc/osmo_bsc_bssap.c
+++ b/src/osmo-bsc/osmo_bsc_bssap.c
@@ -44,6 +44,7 @@
#include <osmocom/core/fsm.h>
#include <osmocom/core/socket.h>
#include <osmocom/core/sockaddr_str.h>
+#include <osmocom/bsc/lcs_loc_req.h>
#define IP_V4_ADDR_LEN 4
@@ -1176,6 +1177,21 @@ static int bssmap_rcvmsg_dt1(struct gsm_subscriber_connection *conn,
rate_ctr_inc(&ctrs[MSC_CTR_BSSMAP_RX_DT1_COMMON_ID]);
ret = bssmap_handle_common_id(conn, msg, length);
break;
+ case BSS_MAP_MSG_PERFORM_LOCATION_RQST:
+ rate_ctr_inc(&ctrs[MSC_CTR_BSSMAP_RX_DT1_PERFORM_LOCATION_REQUEST]);
+ lcs_loc_req_start(conn, msg);
+ ret = 0;
+ break;
+ case BSS_MAP_MSG_PERFORM_LOCATION_ABORT:
+ rate_ctr_inc(&ctrs[MSC_CTR_BSSMAP_RX_DT1_PERFORM_LOCATION_ABORT]);
+ if (conn->lcs.loc_req) {
+ ret = osmo_fsm_inst_dispatch(conn->lcs.loc_req->fi, LCS_LOC_REQ_EV_RX_A_PERFORM_LOCATION_ABORT,
+ msg);
+ } else {
+ LOGP(DMSC, LOGL_ERROR, "Rx BSSMAP Perform Location Abort without ongoing Location Request\n");
+ ret = 0;
+ }
+ break;
default:
rate_ctr_inc(&ctrs[MSC_CTR_BSSMAP_RX_DT1_UNKNOWN]);
LOGP(DMSC, LOGL_NOTICE, "Unimplemented msg type: %s\n",
diff --git a/src/osmo-bsc/osmo_bsc_main.c b/src/osmo-bsc/osmo_bsc_main.c
index ce9df1d29..26d32d14e 100644
--- a/src/osmo-bsc/osmo_bsc_main.c
+++ b/src/osmo-bsc/osmo_bsc_main.c
@@ -38,6 +38,7 @@
#include <osmocom/bsc/assignment_fsm.h>
#include <osmocom/bsc/handover_fsm.h>
#include <osmocom/bsc/smscb.h>
+#include <osmocom/bsc/lb.h>
#include <osmocom/ctrl/control_cmd.h>
#include <osmocom/ctrl/control_if.h>
@@ -788,8 +789,12 @@ static const struct log_info_cat osmo_bsc_categories[] = {
.name = "DCBS",
.description = "Cell Broadcast System",
.enabled = 1, .loglevel = LOGL_NOTICE,
- }
-
+ },
+ [DLCS] = {
+ .name = "DLCS",
+ .description = "Location Services",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
};
static int filter_fn(const struct log_context *ctx, struct log_target *tar)
@@ -947,6 +952,7 @@ int main(int argc, char **argv)
handover_decision_1_init();
hodec2_init(bsc_gsmnet);
bsc_cbc_link_restart();
+ lb_init();
signal(SIGINT, &signal_handler);
signal(SIGTERM, &signal_handler);
diff --git a/src/osmo-bsc/osmo_bsc_msc.c b/src/osmo-bsc/osmo_bsc_msc.c
index 26d2bc8d2..583b6ff23 100644
--- a/src/osmo-bsc/osmo_bsc_msc.c
+++ b/src/osmo-bsc/osmo_bsc_msc.c
@@ -62,6 +62,8 @@ static const struct rate_ctr_desc msc_ctr_description[] = {
[MSC_CTR_BSSMAP_RX_DT1_UNKNOWN] = {"bssmap:rx:dt1:err_unknown", "Number of received BSSMAP unknown DT1 messages"},
[MSC_CTR_BSSMAP_RX_DT1_DTAP] = {"bssmap:rx:dt1:dtap:good", "Number of received BSSMAP DTAP messages"},
[MSC_CTR_BSSMAP_RX_DT1_DTAP_ERROR] = {"bssmap:rx:dt1:dtap:error", "Number of received BSSMAP DTAP messages with errors"},
+ [MSC_CTR_BSSMAP_RX_DT1_PERFORM_LOCATION_REQUEST] = {"bssmap:rx:dt1:location:request", "Number of received BSSMAP Perform Location Request messages"},
+ [MSC_CTR_BSSMAP_RX_DT1_PERFORM_LOCATION_ABORT] = {"bssmap:tx:dt1:location:abort", "Number of received BSSMAP Perform Location Abort messages"},
/* Tx message counters (per message type)
*
@@ -102,6 +104,10 @@ static const struct rate_ctr_desc msc_ctr_description[] = {
[MSC_CTR_BSSMAP_TX_DT1_HANDOVER_COMPLETE] = {"bssmap:tx:dt1:handover:complete", "Number of transmitted BSSMAP DT1 HANDOVER COMPLETE messages"},
[MSC_CTR_BSSMAP_TX_DT1_HANDOVER_FAILURE] = {"bssmap:tx:dt1:handover:failure", "Number of transmitted BSSMAP DT1 HANDOVER FAILURE messages"},
[MSC_CTR_BSSMAP_TX_DT1_DTAP] = {"bssmap:tx:dt1:dtap", "Number of transmitted BSSMAP DT1 DTAP messages"},
+ [MSC_CTR_BSSMAP_TX_DT1_PERFORM_LOCATION_RESPONSE_SUCCESS] = {"bssmap:tx:dt1:location:response_success",
+ "Number of transmitted BSSMAP Perform Location Response messages containing a location estimate"},
+ [MSC_CTR_BSSMAP_TX_DT1_PERFORM_LOCATION_RESPONSE_FAILURE] = {"bssmap:tx:dt1:location:response_failure",
+ "Number of transmitted BSSMAP Perform Location Response messages containing a failure cause"},
/* Indicators for MSC pool usage */
[MSC_CTR_MSCPOOL_SUBSCR_NEW] = {
diff --git a/src/osmo-bsc/paging.c b/src/osmo-bsc/paging.c
index a4a5a1e68..15aca00ea 100644
--- a/src/osmo-bsc/paging.c
+++ b/src/osmo-bsc/paging.c
@@ -79,9 +79,9 @@ static void page_ms(struct gsm_paging_request *request)
log_set_context(LOG_CTX_BSC_SUBSCR, request->bsub);
- LOG_BTS(bts, DPAG, LOGL_INFO, "Going to send paging commands: imsi: %s tmsi: "
- "0x%08x for ch. type %d (attempt %d)\n", request->bsub->imsi,
- request->bsub->tmsi, request->chan_type, request->attempts);
+ LOG_BTS(bts, DPAG, LOGL_INFO, "Going to send paging commands: %s"
+ " for ch. type %d (attempt %d)\n", bsc_subscr_name(request->bsub),
+ request->chan_type, request->attempts);
if (request->bsub->tmsi == GSM_RESERVED_TMSI) {
mi = (struct osmo_mobile_identity){
@@ -457,6 +457,30 @@ int paging_request_stop(struct bsc_msc_data **msc_p, enum bsc_paging_reason *rea
return count;
}
+/* Remove all paging requests, for specific reasons only. */
+int paging_request_cancel(struct bsc_subscr *bsub, enum bsc_paging_reason reasons)
+{
+ struct gsm_bts *bts;
+ int count = 0;
+
+ llist_for_each_entry(bts, &bsc_gsmnet->bts_list, list) {
+ struct gsm_paging_request *req, *req2;
+
+ paging_init_if_needed(bts);
+
+ llist_for_each_entry_safe(req, req2, &bts->paging.pending_requests, entry) {
+ if (req->bsub != bsub)
+ continue;
+ if (!(req->reason & reasons))
+ continue;
+ LOG_BTS(bts, DPAG, LOGL_DEBUG, "Cancel paging %s\n", bsc_subscr_name(bsub));
+ paging_remove_request(&bts->paging, req);
+ count++;
+ }
+ }
+ return count;
+}
+
/*! Update the BTS paging buffer slots on given BTS */
void paging_update_buffer_space(struct gsm_bts *bts, uint16_t free_slots)
{