osmo-smlc/src/osmo-smlc/smlc_loc_req.c

442 lines
14 KiB
C

/* 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>
*
* SPDX-License-Identifier: GPL-2.0+
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 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 General Public License for more details.
*
*/
#include <osmocom/smlc/smlc_data.h>
#include <osmocom/smlc/smlc_loc_req.h>
#include <osmocom/smlc/smlc_subscr.h>
#include <osmocom/smlc/lb_conn.h>
#include <osmocom/smlc/cell_locations.h>
#include <osmocom/core/fsm.h>
#include <osmocom/core/tdef.h>
#include <osmocom/core/utils.h>
#include <osmocom/gsm/bsslap.h>
#include <osmocom/gsm/bssmap_le.h>
#include <osmocom/gsm/gad.h>
enum smlc_loc_req_fsm_state {
SMLC_LOC_REQ_ST_INIT,
SMLC_LOC_REQ_ST_WAIT_TA,
SMLC_LOC_REQ_ST_GOT_TA,
SMLC_LOC_REQ_ST_FAILED,
};
static const struct value_string smlc_loc_req_fsm_event_names[] = {
OSMO_VALUE_STRING(SMLC_LOC_REQ_EV_RX_TA_RESPONSE),
OSMO_VALUE_STRING(SMLC_LOC_REQ_EV_RX_BSSLAP_RESET),
OSMO_VALUE_STRING(SMLC_LOC_REQ_EV_RX_LE_PERFORM_LOCATION_ABORT),
{}
};
static struct osmo_fsm smlc_loc_req_fsm;
static const struct osmo_tdef_state_timeout smlc_loc_req_fsm_timeouts[32] = {
[SMLC_LOC_REQ_ST_WAIT_TA] = { .T = -12 },
};
/* Transition to a state, using the T timer defined in smlc_loc_req_fsm_timeouts.
* The actual timeout value is in turn obtained from network->T_defs.
* Assumes local variable fi exists. */
#define smlc_loc_req_fsm_state_chg(FI, STATE) \
osmo_tdef_fsm_inst_state_chg(FI, STATE, \
smlc_loc_req_fsm_timeouts, \
g_smlc_tdefs, \
5)
#define smlc_loc_req_fail(cause, fmt, args...) do { \
LOG_SMLC_LOC_REQ(smlc_loc_req, LOGL_ERROR, "Perform Location Request failed in state %s: " fmt "\n", \
smlc_loc_req ? osmo_fsm_inst_state_name(smlc_loc_req->fi) : "NULL", ## args); \
smlc_loc_req->lcs_cause = (struct lcs_cause_ie){ \
.present = true, \
.cause_val = cause, \
}; \
smlc_loc_req_fsm_state_chg(smlc_loc_req->fi, SMLC_LOC_REQ_ST_FAILED); \
} while(0)
static struct smlc_loc_req *smlc_loc_req_alloc(void *ctx)
{
struct smlc_loc_req *smlc_loc_req;
struct osmo_fsm_inst *fi = osmo_fsm_inst_alloc(&smlc_loc_req_fsm, ctx, NULL, LOGL_DEBUG, "no-id");
OSMO_ASSERT(fi);
smlc_loc_req = talloc(fi, struct smlc_loc_req);
OSMO_ASSERT(smlc_loc_req);
fi->priv = smlc_loc_req;
*smlc_loc_req = (struct smlc_loc_req){
.fi = fi,
};
return smlc_loc_req;
}
static int smlc_loc_req_start(struct lb_conn *lb_conn, const struct bssmap_le_perform_loc_req *loc_req_pdu)
{
struct smlc_loc_req *smlc_loc_req;
rate_ctr_inc(rate_ctr_group_get_ctr(g_smlc->ctrs, SMLC_CTR_BSSMAP_LE_RX_DT1_PERFORM_LOCATION_REQUEST));
if (lb_conn->smlc_loc_req) {
/* Another request is already pending. If we send Perform Location Abort, the peer doesn't know which
* request we would mean. Just drop this on the floor. */
LOG_SMLC_LOC_REQ(lb_conn->smlc_loc_req, LOGL_ERROR,
"Ignoring Perform Location Request, another request is still pending\n");
return -EAGAIN;
}
if (loc_req_pdu->imsi.type == GSM_MI_TYPE_IMSI
&& (!lb_conn->smlc_subscr
|| osmo_mobile_identity_cmp(&loc_req_pdu->imsi, &lb_conn->smlc_subscr->imsi))) {
struct smlc_subscr *smlc_subscr;
struct lb_conn *other_conn;
smlc_subscr = smlc_subscr_find_or_create(&loc_req_pdu->imsi, __func__);
OSMO_ASSERT(smlc_subscr);
if (lb_conn->smlc_subscr && lb_conn->smlc_subscr != smlc_subscr) {
LOG_LB_CONN(lb_conn, LOGL_ERROR,
"IMSI mismatch: lb_conn has %s, Rx Perform Location Request has %s\n",
smlc_subscr_to_str_c(OTC_SELECT, lb_conn->smlc_subscr),
smlc_subscr_to_str_c(OTC_SELECT, smlc_subscr));
smlc_subscr_put(smlc_subscr, __func__);
return -EINVAL;
}
/* Find another conn before setting this conn's subscriber */
other_conn = lb_conn_find_by_smlc_subscr(lb_conn->smlc_subscr, __func__);
/* Set the subscriber before logging about it, so that it shows as log context */
if (!lb_conn->smlc_subscr) {
lb_conn->smlc_subscr = smlc_subscr;
smlc_subscr_get(lb_conn->smlc_subscr, SMLC_SUBSCR_USE_LB_CONN);
}
if (other_conn && other_conn != lb_conn) {
LOG_LB_CONN(lb_conn, LOGL_ERROR, "Another conn already active for this subscriber\n");
LOG_LB_CONN(other_conn, LOGL_ERROR, "Another conn opened for this subscriber, discarding\n");
lb_conn_close(other_conn);
}
smlc_subscr_put(smlc_subscr, __func__);
if (other_conn)
lb_conn_put(other_conn, __func__);
}
/* smlc_loc_req has a use count on lb_conn, so its talloc ctx must not be a child of lb_conn. (Otherwise an
* lb_conn_put() from smlc_loc_req could cause a free of smlc_loc_req's parent ctx, causing a use after free on
* FSM termination.) */
smlc_loc_req = smlc_loc_req_alloc(lb_conn->lb_peer);
*smlc_loc_req = (struct smlc_loc_req){
.fi = smlc_loc_req->fi,
.lb_conn = lb_conn,
.req = *loc_req_pdu,
};
smlc_loc_req->latest_cell_id = loc_req_pdu->cell_id;
lb_conn->smlc_loc_req = smlc_loc_req;
lb_conn_get(smlc_loc_req->lb_conn, LB_CONN_USE_SMLC_LOC_REQ);
LOG_LB_CONN(lb_conn, LOGL_INFO, "Rx Perform Location Request (BSSLAP APDU %s), cell id is %s\n",
loc_req_pdu->apdu_present ?
osmo_bsslap_msgt_name(loc_req_pdu->apdu.msg_type) : "omitted",
gsm0808_cell_id_name_c(OTC_SELECT, &smlc_loc_req->latest_cell_id));
/* state change to start the timeout */
smlc_loc_req_fsm_state_chg(smlc_loc_req->fi, SMLC_LOC_REQ_ST_WAIT_TA);
return 0;
}
static int handle_bssmap_le_conn_oriented_info(struct smlc_loc_req *smlc_loc_req,
const struct bssmap_le_conn_oriented_info *coi)
{
switch (coi->apdu.msg_type) {
case BSSLAP_MSGT_TA_RESPONSE:
return osmo_fsm_inst_dispatch(smlc_loc_req->fi, SMLC_LOC_REQ_EV_RX_TA_RESPONSE,
(void*)&coi->apdu.ta_response);
case BSSLAP_MSGT_RESET:
return osmo_fsm_inst_dispatch(smlc_loc_req->fi, SMLC_LOC_REQ_EV_RX_BSSLAP_RESET,
(void*)&coi->apdu.reset);
case BSSLAP_MSGT_ABORT:
smlc_loc_req_fail(LCS_CAUSE_REQUEST_ABORTED, "Aborting Location Request due to BSSLAP Abort");
return 0;
case BSSLAP_MSGT_REJECT:
smlc_loc_req_fail(LCS_CAUSE_REQUEST_ABORTED, "Aborting Location Request due to BSSLAP Reject");
return 0;
default:
LOG_SMLC_LOC_REQ(smlc_loc_req, LOGL_ERROR, "rx BSSLAP APDU with unsupported message type %s\n",
osmo_bsslap_msgt_name(coi->apdu.msg_type));
return -ENOTSUP;
};
}
int smlc_loc_req_rx_bssap_le(struct lb_conn *lb_conn, const struct bssap_le_pdu *bssap_le)
{
struct smlc_loc_req *smlc_loc_req = lb_conn->smlc_loc_req;
const struct bssmap_le_pdu *bssmap_le = &bssap_le->bssmap_le;
LOG_LB_CONN(lb_conn, LOGL_DEBUG, "Rx %s\n", osmo_bssap_le_pdu_to_str_c(OTC_SELECT, bssap_le));
if (bssap_le->discr != BSSAP_LE_MSG_DISCR_BSSMAP_LE) {
LOG_LB_CONN(lb_conn, LOGL_ERROR, "BSSAP-LE discr %d not implemented\n", bssap_le->discr);
return -ENOTSUP;
}
switch (bssmap_le->msg_type) {
case BSSMAP_LE_MSGT_PERFORM_LOC_REQ:
return smlc_loc_req_start(lb_conn, &bssmap_le->perform_loc_req);
case BSSMAP_LE_MSGT_PERFORM_LOC_ABORT:
return osmo_fsm_inst_dispatch(smlc_loc_req->fi, SMLC_LOC_REQ_EV_RX_LE_PERFORM_LOCATION_ABORT,
(void*)&bssmap_le->perform_loc_abort);
case BSSMAP_LE_MSGT_CONN_ORIENTED_INFO:
return handle_bssmap_le_conn_oriented_info(smlc_loc_req, &bssmap_le->conn_oriented_info);
default:
LOG_SMLC_LOC_REQ(smlc_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 smlc_loc_req_reset(struct lb_conn *lb_conn)
{
struct smlc_loc_req *smlc_loc_req = lb_conn->smlc_loc_req;
if (!smlc_loc_req)
return;
smlc_loc_req_fail(LCS_CAUSE_SYSTEM_FAILURE, "Aborting Location Request due to RESET on Lb");
}
static int smlc_loc_req_fsm_timer_cb(struct osmo_fsm_inst *fi)
{
struct smlc_loc_req *smlc_loc_req = fi->priv;
smlc_loc_req_fail(LCS_CAUSE_SYSTEM_FAILURE, "Timeout");
return 1;
}
static void smlc_loc_req_wait_ta_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
struct smlc_loc_req *smlc_loc_req = fi->priv;
struct bssmap_le_pdu bssmap_le;
/* Did the original request contain a TA already? */
if (smlc_loc_req->req.apdu_present && smlc_loc_req->req.apdu.msg_type == BSSLAP_MSGT_TA_LAYER3) {
smlc_loc_req->ta_present = true;
smlc_loc_req->ta = smlc_loc_req->req.apdu.ta_layer3.ta;
LOG_SMLC_LOC_REQ(smlc_loc_req, LOGL_INFO, "TA = %u\n", smlc_loc_req->ta);
smlc_loc_req_fsm_state_chg(smlc_loc_req->fi, SMLC_LOC_REQ_ST_GOT_TA);
return;
}
/* No TA known yet, ask via BSSLAP */
bssmap_le = (struct bssmap_le_pdu){
.msg_type = BSSMAP_LE_MSGT_CONN_ORIENTED_INFO,
.conn_oriented_info = {
.apdu = {
.msg_type = BSSLAP_MSGT_TA_REQUEST,
},
},
};
lb_conn_send_bssmap_le(smlc_loc_req->lb_conn, &bssmap_le);
}
static void update_ci(struct gsm0808_cell_id *cell_id, int16_t new_ci)
{
struct osmo_cell_global_id cgi = {};
struct gsm0808_cell_id ci = {
.id_discr = CELL_IDENT_CI,
.id.ci = new_ci,
};
/* Set all values from the cell_id to the cgi */
gsm0808_cell_id_to_cgi(&cgi, cell_id);
/* Overwrite the CI part */
gsm0808_cell_id_to_cgi(&cgi, &ci);
/* write back to cell_id, without changing its type */
gsm0808_cell_id_from_cgi(cell_id, cell_id->id_discr, &cgi);
}
static void smlc_loc_req_wait_ta_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct smlc_loc_req *smlc_loc_req = fi->priv;
const struct bsslap_ta_response *ta_response;
const struct bsslap_reset *reset;
switch (event) {
case SMLC_LOC_REQ_EV_RX_TA_RESPONSE:
ta_response = data;
smlc_loc_req->ta_present = true;
smlc_loc_req->ta = ta_response->ta;
update_ci(&smlc_loc_req->latest_cell_id, ta_response->cell_id);
LOG_SMLC_LOC_REQ(smlc_loc_req, LOGL_INFO, "Rx BSSLAP TA Response: cell id is now %s\n",
gsm0808_cell_id_name_c(OTC_SELECT, &smlc_loc_req->latest_cell_id));
smlc_loc_req_fsm_state_chg(smlc_loc_req->fi, SMLC_LOC_REQ_ST_GOT_TA);
return;
case SMLC_LOC_REQ_EV_RX_BSSLAP_RESET:
reset = data;
smlc_loc_req->ta_present = true;
smlc_loc_req->ta = reset->ta;
update_ci(&smlc_loc_req->latest_cell_id, reset->cell_id);
LOG_SMLC_LOC_REQ(smlc_loc_req, LOGL_INFO, "Rx BSSLAP Reset: cell id is now %s\n",
gsm0808_cell_id_name_c(OTC_SELECT, &smlc_loc_req->latest_cell_id));
smlc_loc_req_fsm_state_chg(smlc_loc_req->fi, SMLC_LOC_REQ_ST_GOT_TA);
return;
case SMLC_LOC_REQ_EV_RX_LE_PERFORM_LOCATION_ABORT:
LOG_SMLC_LOC_REQ(smlc_loc_req, LOGL_INFO, "Rx Perform Location Abort, stopping this request dead\n");
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REQUEST, NULL);
return;
default:
OSMO_ASSERT(false);
}
}
static void smlc_loc_req_got_ta_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
struct smlc_loc_req *smlc_loc_req = fi->priv;
struct bssmap_le_pdu bssmap_le;
struct osmo_gad location;
int rc;
if (!smlc_loc_req->ta_present) {
smlc_loc_req_fail(LCS_CAUSE_SYSTEM_FAILURE,
"Internal error: GOT_TA event, but no TA present");
return;
}
bssmap_le = (struct bssmap_le_pdu){
.msg_type = BSSMAP_LE_MSGT_PERFORM_LOC_RESP,
.perform_loc_resp = {
.location_estimate_present = true,
},
};
rc = cell_location_from_ta(&location, &smlc_loc_req->latest_cell_id, smlc_loc_req->ta);
if (rc) {
smlc_loc_req_fail(LCS_CAUSE_FACILITY_NOTSUPP, "Unable to compose Location Estimate for %s: %s",
gsm0808_cell_id_name_c(OTC_SELECT, &smlc_loc_req->latest_cell_id),
rc == -ENOENT ? "No location information for this cell" : "unknown error");
return;
}
rc = osmo_gad_enc(&bssmap_le.perform_loc_resp.location_estimate, &location);
if (rc <= 0) {
smlc_loc_req_fail(LCS_CAUSE_FACILITY_NOTSUPP, "Unable to encode Location Estimate for %s (rc=%d)",
gsm0808_cell_id_name_c(OTC_SELECT, &smlc_loc_req->latest_cell_id), rc);
return;
}
LOG_SMLC_LOC_REQ(smlc_loc_req, LOGL_INFO, "Returning location estimate to BSC: %s TA=%u --> %s\n",
gsm0808_cell_id_name_c(OTC_SELECT, &smlc_loc_req->latest_cell_id),
smlc_loc_req->ta, osmo_gad_to_str_c(OTC_SELECT, &location));
if (lb_conn_send_bssmap_le(smlc_loc_req->lb_conn, &bssmap_le)) {
smlc_loc_req_fail(LCS_CAUSE_SYSTEM_FAILURE,
"Unable to encode/send BSSMAP-LE Perform Location Response");
return;
}
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
}
static void smlc_loc_req_failed_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
struct smlc_loc_req *smlc_loc_req = fi->priv;
struct bssmap_le_pdu bssmap_le = {
.msg_type = BSSMAP_LE_MSGT_PERFORM_LOC_RESP,
.perform_loc_resp = {
.lcs_cause = smlc_loc_req->lcs_cause,
},
};
int rc;
rc = lb_conn_send_bssmap_le(smlc_loc_req->lb_conn, &bssmap_le);
osmo_fsm_inst_term(fi, rc ? OSMO_FSM_TERM_ERROR : OSMO_FSM_TERM_REGULAR, NULL);
}
void smlc_loc_req_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
{
struct smlc_loc_req *smlc_loc_req = fi->priv;
if (smlc_loc_req->lb_conn && smlc_loc_req->lb_conn->smlc_loc_req == smlc_loc_req) {
smlc_loc_req->lb_conn->smlc_loc_req = NULL;
lb_conn_put(smlc_loc_req->lb_conn, LB_CONN_USE_SMLC_LOC_REQ);
}
}
#define S(x) (1 << (x))
static const struct osmo_fsm_state smlc_loc_req_fsm_states[] = {
[SMLC_LOC_REQ_ST_INIT] = {
.name = "INIT",
.out_state_mask = 0
| S(SMLC_LOC_REQ_ST_WAIT_TA)
| S(SMLC_LOC_REQ_ST_FAILED)
,
},
[SMLC_LOC_REQ_ST_WAIT_TA] = {
.name = "WAIT_TA",
.in_event_mask = 0
| S(SMLC_LOC_REQ_EV_RX_TA_RESPONSE)
| S(SMLC_LOC_REQ_EV_RX_BSSLAP_RESET)
| S(SMLC_LOC_REQ_EV_RX_LE_PERFORM_LOCATION_ABORT)
,
.out_state_mask = 0
| S(SMLC_LOC_REQ_ST_GOT_TA)
| S(SMLC_LOC_REQ_ST_FAILED)
,
.onenter = smlc_loc_req_wait_ta_onenter,
.action = smlc_loc_req_wait_ta_action,
},
[SMLC_LOC_REQ_ST_GOT_TA] = {
.name = "GOT_TA",
.out_state_mask = 0
| S(SMLC_LOC_REQ_ST_FAILED)
,
.onenter = smlc_loc_req_got_ta_onenter,
},
[SMLC_LOC_REQ_ST_FAILED] = {
.name = "FAILED",
.onenter = smlc_loc_req_failed_onenter,
},
};
static struct osmo_fsm smlc_loc_req_fsm = {
.name = "smlc_loc_req",
.states = smlc_loc_req_fsm_states,
.num_states = ARRAY_SIZE(smlc_loc_req_fsm_states),
.log_subsys = DLCS,
.event_names = smlc_loc_req_fsm_event_names,
.timer_cb = smlc_loc_req_fsm_timer_cb,
.cleanup = smlc_loc_req_fsm_cleanup,
};
static __attribute__((constructor)) void smlc_loc_req_fsm_register(void)
{
OSMO_ASSERT(osmo_fsm_register(&smlc_loc_req_fsm) == 0);
}