From 4ae338d5b6d737fd37826c02e27e2553cf23e2a3 Mon Sep 17 00:00:00 2001 From: Neels Hofmeyr Date: Thu, 17 Sep 2020 17:54:39 +0200 Subject: LCS: implement the bulk of Location Services Depends: I4d7302a4853518916b6b425e710c10568eb2ffe5 (libosmocore) Change-Id: I28314ba97df86a118497e9b2770e2e6e2484e872 --- src/osmo-bsc/Makefile.am | 3 + src/osmo-bsc/bsc_init.c | 1 + src/osmo-bsc/bsc_sccp.c | 9 +- src/osmo-bsc/bsc_subscr_conn_fsm.c | 60 +++- src/osmo-bsc/gsm_04_08_rr.c | 1 - src/osmo-bsc/gsm_08_08.c | 7 + src/osmo-bsc/gsm_data.c | 1 + src/osmo-bsc/handover_fsm.c | 5 + src/osmo-bsc/lb.c | 662 +++++++++++++++++++++++++++++++++++++ src/osmo-bsc/lcs_loc_req.c | 581 ++++++++++++++++++++++++++++++++ src/osmo-bsc/lcs_ta_req.c | 305 +++++++++++++++++ src/osmo-bsc/net_init.c | 1 + src/osmo-bsc/osmo_bsc_bssap.c | 16 + src/osmo-bsc/osmo_bsc_main.c | 10 +- src/osmo-bsc/osmo_bsc_msc.c | 6 + src/osmo-bsc/paging.c | 30 +- 16 files changed, 1681 insertions(+), 17 deletions(-) create mode 100644 src/osmo-bsc/lb.c create mode 100644 src/osmo-bsc/lcs_loc_req.c create mode 100644 src/osmo-bsc/lcs_ta_req.c (limited to 'src/osmo-bsc') 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 #include #include +#include #include #include 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 #include +#include /* 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 #include #include +#include +#include #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 #include +#include +#include + #include #include #include @@ -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 #include #include +#include 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 #include #include +#include #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 + * + * All Rights Reserved + * + * Author: Neels Hofmeyr + * + * 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 . + * + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +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 + * All Rights Reserved + * + * Author: Neels Hofmeyr + * + * 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 . + * + */ + + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 + * All Rights Reserved + * + * Author: Neels Hofmeyr + * + * 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 . + * + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 #include #include +#include #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 #include #include +#include #include #include @@ -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) { -- cgit v1.2.3