This is merely a historical archive of years 2008-2021, before the migration to mailman3.
A maintained and still updated list archive can be found at https://lists.osmocom.org/hyperkitty/list/gerrit-log@lists.osmocom.org/.
neels gerrit-no-reply at lists.osmocom.orgneels has uploaded this change for review. ( https://gerrit.osmocom.org/c/osmo-bsc/+/20357 ) Change subject: LCS: implement the bulk of Location Services ...................................................................... LCS: implement the bulk of Location Services Change-Id: I28314ba97df86a118497e9b2770e2e6e2484e872 --- M include/osmocom/bsc/bsc_msc_data.h M include/osmocom/bsc/bsc_subscr_conn_fsm.h M include/osmocom/bsc/debug.h M include/osmocom/bsc/gsm_data.h A include/osmocom/bsc/lb.h A include/osmocom/bsc/lcs_loc_req.h A include/osmocom/bsc/lcs_ta_req.h M include/osmocom/bsc/paging.h M include/osmocom/bsc/vty.h M src/osmo-bsc/Makefile.am M src/osmo-bsc/bsc_init.c M src/osmo-bsc/bsc_sccp.c M src/osmo-bsc/bsc_subscr_conn_fsm.c M src/osmo-bsc/gsm_04_08_rr.c M src/osmo-bsc/gsm_08_08.c M src/osmo-bsc/gsm_data.c M src/osmo-bsc/handover_fsm.c A src/osmo-bsc/lb.c A src/osmo-bsc/lcs.c A src/osmo-bsc/lcs_loc_req.c A src/osmo-bsc/lcs_ta_req.c M src/osmo-bsc/net_init.c M src/osmo-bsc/osmo_bsc_bssap.c M src/osmo-bsc/osmo_bsc_main.c M src/osmo-bsc/osmo_bsc_msc.c M src/osmo-bsc/paging.c M tests/bsc/bsc_test.c M tests/handover/Makefile.am 28 files changed, 1,989 insertions(+), 17 deletions(-) git pull ssh://gerrit.osmocom.org:29418/osmo-bsc refs/changes/57/20357/1 diff --git a/include/osmocom/bsc/bsc_msc_data.h b/include/osmocom/bsc/bsc_msc_data.h index 43ace25..5699b77 100644 --- a/include/osmocom/bsc/bsc_msc_data.h +++ b/include/osmocom/bsc/bsc_msc_data.h @@ -68,6 +68,8 @@ MSC_CTR_BSSMAP_RX_DT1_UNKNOWN, MSC_CTR_BSSMAP_RX_DT1_DTAP, MSC_CTR_BSSMAP_RX_DT1_DTAP_ERROR, + MSC_CTR_BSSMAP_RX_DT1_PERFORM_LOCATION_REQUEST, + MSC_CTR_BSSMAP_RX_DT1_PERFORM_LOCATION_ABORT, /* Tx message counters (per connection type) */ MSC_CTR_BSSMAP_TX_BSS_MANAGEMENT, @@ -97,6 +99,8 @@ MSC_CTR_BSSMAP_TX_DT1_HANDOVER_COMPLETE, MSC_CTR_BSSMAP_TX_DT1_HANDOVER_FAILURE, MSC_CTR_BSSMAP_TX_DT1_DTAP, + MSC_CTR_BSSMAP_TX_DT1_PERFORM_LOCATION_RESPONSE_SUCCESS, + MSC_CTR_BSSMAP_TX_DT1_PERFORM_LOCATION_RESPONSE_FAILURE, MSC_CTR_MSCPOOL_SUBSCR_NEW, MSC_CTR_MSCPOOL_SUBSCR_REATTACH, diff --git a/include/osmocom/bsc/bsc_subscr_conn_fsm.h b/include/osmocom/bsc/bsc_subscr_conn_fsm.h index ccac4fa..142d535 100644 --- a/include/osmocom/bsc/bsc_subscr_conn_fsm.h +++ b/include/osmocom/bsc/bsc_subscr_conn_fsm.h @@ -44,6 +44,8 @@ GSCON_EV_FORGET_LCHAN, GSCON_EV_FORGET_MGW_ENDPOINT, + + GSCON_EV_LCS_LOC_REQ_END, }; struct gscon_clear_cmd_data { diff --git a/include/osmocom/bsc/debug.h b/include/osmocom/bsc/debug.h index 82c0703..0380b74 100644 --- a/include/osmocom/bsc/debug.h +++ b/include/osmocom/bsc/debug.h @@ -27,6 +27,7 @@ DTS, DAS, DCBS, + DLCS, Debug_LastEntry, }; diff --git a/include/osmocom/bsc/gsm_data.h b/include/osmocom/bsc/gsm_data.h index 1ea0172..03e4e83 100644 --- a/include/osmocom/bsc/gsm_data.h +++ b/include/osmocom/bsc/gsm_data.h @@ -33,6 +33,8 @@ #include <osmocom/bsc/neighbor_ident.h> #include <osmocom/bsc/osmux.h> +#include <osmocom/sigtran/sccp_sap.h> + #define GSM_T3122_DEFAULT 10 struct mgcp_client_conf; @@ -303,6 +305,23 @@ uint8_t ms_power_class:3; bool rx_clear_command; + + /* Location Services handling for this subscriber */ + struct { + /* FSM to handle Perform Location Request coming in from the MSC via A interface, + * and receive BSSMAP-LE responses from the SMLC. */ + struct lcs_loc_req *loc_req; + + /* FSM to handle BSSLAP requests coming in from the SMLC via Lb interface. + * BSSLAP APDU are encapsulated in BSSMAP-LE Connection Oriented Information messages. */ + struct lcs_bsslap *bsslap; + + /* Lb interface to the SMLC: BSSMAP-LE/SCCP connection associated with this subscriber */ + struct { + int conn_id; + enum subscr_sccp_state state; + } lb; + } lcs; }; @@ -1201,6 +1220,24 @@ uint8_t nri_bitlen; struct osmo_nri_ranges *null_nri_ranges; + + struct { + uint32_t cs7_instance; + bool cs7_instance_valid; + struct osmo_sccp_instance *sccp; + struct osmo_sccp_user *sccp_user; + + struct osmo_sccp_addr bsc_addr; + char *bsc_addr_name; + + struct osmo_sccp_addr smlc_addr; + char *smlc_addr_name; + + /*! True after either side has sent a BSSMAP-LE RESET-ACK */ + bool ready; + + struct rate_ctr_group *ctrs; + } smlc; }; struct gsm_audio_support { diff --git a/include/osmocom/bsc/lb.h b/include/osmocom/bsc/lb.h new file mode 100644 index 0000000..ac7f821 --- /dev/null +++ b/include/osmocom/bsc/lb.h @@ -0,0 +1,39 @@ +/* Location Services (LCS): low level Lb/SCCP handling in OsmoBSC, API */ +#pragma once + +#include <osmocom/core/rate_ctr.h> + +struct osmo_bssap_le_pdu; +struct gsm_subscriber_connection; + +enum { + SMLC_CTR_BSSMAP_LE_RX_UNKNOWN_PEER, + SMLC_CTR_BSSMAP_LE_RX_UDT_RESET, + SMLC_CTR_BSSMAP_LE_RX_UDT_RESET_ACK, + SMLC_CTR_BSSMAP_LE_RX_UDT_ERR_INVALID_MSG, + SMLC_CTR_BSSMAP_LE_RX_DT1_ERR_INVALID_MSG, + SMLC_CTR_BSSMAP_LE_RX_DT1_PERFORM_LOCATION_RESPONSE_SUCCESS, + SMLC_CTR_BSSMAP_LE_RX_DT1_PERFORM_LOCATION_RESPONSE_FAILURE, + SMLC_CTR_BSSMAP_LE_RX_DT1_BSSLAP_TA_REQUEST, + + SMLC_CTR_BSSMAP_LE_TX_ERR_INVALID_MSG, + SMLC_CTR_BSSMAP_LE_TX_ERR_CONN_NOT_READY, + SMLC_CTR_BSSMAP_LE_TX_ERR_SEND, + SMLC_CTR_BSSMAP_LE_TX_SUCCESS, + + SMLC_CTR_BSSMAP_LE_TX_UDT_RESET, + SMLC_CTR_BSSMAP_LE_TX_UDT_RESET_ACK, + SMLC_CTR_BSSMAP_LE_TX_DT1_PERFORM_LOCATION_REQUEST, + SMLC_CTR_BSSMAP_LE_TX_DT1_PERFORM_LOCATION_ABORT, + SMLC_CTR_BSSMAP_LE_TX_DT1_BSSLAP_TA_RESPONSE, + SMLC_CTR_BSSMAP_LE_TX_DT1_BSSLAP_REJECT, + SMLC_CTR_BSSMAP_LE_TX_DT1_BSSLAP_RESET, + SMLC_CTR_BSSMAP_LE_TX_DT1_BSSLAP_ABORT, +}; + +extern const struct rate_ctr_desc smlc_ctr_description[]; +extern const struct rate_ctr_group_desc smlc_ctrg_desc; + +int lb_init(); +int lb_send(struct gsm_subscriber_connection *conn, const struct osmo_bssap_le_pdu *bssap_le); +void lb_close_conn(struct gsm_subscriber_connection *conn); diff --git a/include/osmocom/bsc/lcs_loc_req.h b/include/osmocom/bsc/lcs_loc_req.h new file mode 100644 index 0000000..26dd926 --- /dev/null +++ b/include/osmocom/bsc/lcs_loc_req.h @@ -0,0 +1,48 @@ +/* Location Services (LCS): BSSMAP and BSSMAP-LE Perform Location Request handling in OsmoBSC, API */ +#pragma once + +#include <osmocom/gsm/bssmap_le.h> + +#define LOG_LCS_LOC_REQ(LOC_REQ, level, fmt, args...) do { \ + if (LOC_REQ) \ + LOGPFSML(LOC_REQ->fi, level, fmt, ## args); \ + else \ + LOGP(DLCS, level, "LCS Perf Loc Req: " fmt, ## args); \ + } while(0) + +struct lcs_ta_req; + +enum lcs_loc_req_fsm_event { + LCS_LOC_REQ_EV_RX_LB_PERFORM_LOCATION_RESPONSE, + LCS_LOC_REQ_EV_RX_A_PERFORM_LOCATION_ABORT, + LCS_LOC_REQ_EV_TA_REQ_START, + LCS_LOC_REQ_EV_TA_REQ_END, + LCS_LOC_REQ_EV_HANDOVER_PERFORMED, + LCS_LOC_REQ_EV_CONN_CLEAR, +}; + +struct lcs_loc_req { + struct osmo_fsm_inst *fi; + struct gsm_subscriber_connection *conn; + + struct { + struct osmo_bssmap_le_location_type location_type; + + bool cell_id_present; + struct gsm0808_cell_id cell_id; + + struct osmo_mobile_identity imsi; + struct osmo_mobile_identity imei; + } req; + + bool resp_present; + struct osmo_bssmap_le_perform_loc_resp resp; + + struct osmo_lcs_cause_ie lcs_cause; + + struct lcs_ta_req *ta_req; +}; + +void lcs_loc_req_start(struct gsm_subscriber_connection *conn, struct msgb *msg); +int lcs_loc_req_rx_bssmap_le(struct gsm_subscriber_connection *conn, struct msgb *msg); +void lcs_loc_req_reset(struct gsm_subscriber_connection *conn); diff --git a/include/osmocom/bsc/lcs_ta_req.h b/include/osmocom/bsc/lcs_ta_req.h new file mode 100644 index 0000000..0ae745c --- /dev/null +++ b/include/osmocom/bsc/lcs_ta_req.h @@ -0,0 +1,29 @@ +/* Location Services (LCS): BSSLAP TA Request handling in OsmoBSC, API */ +#pragma once + +#include <osmocom/bsc/debug.h> +#include <osmocom/bsc/gsm_data.h> +#include <osmocom/core/fsm.h> +#include <osmocom/gsm/bssmap_le.h> + +#define LOG_LCS_TA_REQ(TA_REQ, level, fmt, args...) do { \ + if (TA_REQ) \ + LOGPFSML(TA_REQ->fi, level, fmt, ## args); \ + else \ + LOGP(DLCS, level, "LCS TA Req: " fmt, ## args); \ + } while(0) + +enum lcs_ta_req_fsm_event { + LCS_TA_REQ_EV_GOT_TA, + LCS_TA_REQ_EV_ABORT, +}; + +struct lcs_ta_req { + struct osmo_fsm_inst *fi; + struct lcs_loc_req *loc_req; + enum osmo_lcs_cause failure_cause; + uint8_t failure_diagnostic_val; +}; +int lcs_ta_req_start(struct lcs_loc_req *lcs_loc_req); + +void lcs_bsslap_rx(struct gsm_subscriber_connection *conn, struct msgb *msg); diff --git a/include/osmocom/bsc/paging.h b/include/osmocom/bsc/paging.h index fa3e3e1..288ebf5 100644 --- a/include/osmocom/bsc/paging.h +++ b/include/osmocom/bsc/paging.h @@ -94,6 +94,7 @@ int paging_request_stop(struct bsc_msc_data **msc_p, enum bsc_paging_reason *reasons_p, struct gsm_bts *bts, struct bsc_subscr *bsub); +int paging_request_cancel(struct bsc_subscr *bsub, enum bsc_paging_reason reasons); /* update paging load */ void paging_update_buffer_space(struct gsm_bts *bts, uint16_t); diff --git a/include/osmocom/bsc/vty.h b/include/osmocom/bsc/vty.h index ba44f5e..29c7f52 100644 --- a/include/osmocom/bsc/vty.h +++ b/include/osmocom/bsc/vty.h @@ -27,6 +27,7 @@ CBC_NODE, CBC_SERVER_NODE, CBC_CLIENT_NODE, + SMLC_NODE, }; struct log_info; diff --git a/src/osmo-bsc/Makefile.am b/src/osmo-bsc/Makefile.am index b0fc181..8d109fd 100644 --- a/src/osmo-bsc/Makefile.am +++ b/src/osmo-bsc/Makefile.am @@ -65,9 +65,12 @@ 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 1460af4..7e83953 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> @@ -136,6 +137,8 @@ net->cbc->client.remote_addr = (struct osmo_sockaddr_str){ .port = CBSP_TCP_PORT, }; net->cbc->client.local_addr = (struct osmo_sockaddr_str){}; + net->smlc.ctrs = rate_ctr_group_alloc(net, &smlc_ctrg_desc, 0); + return net; err_free_all: diff --git a/src/osmo-bsc/bsc_sccp.c b/src/osmo-bsc/bsc_sccp.c index 9d4289f..52858fe 100644 --- a/src/osmo-bsc/bsc_sccp.c +++ b/src/osmo-bsc/bsc_sccp.c @@ -47,7 +47,13 @@ } } - /* 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 ba389d2..133cb59 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 @@ {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 @@ 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; - /* Make sure the conn FSM will osmo_sccp_tx_disconn() on term */ - conn->sccp.state = SUBSCR_SCCP_ST_CONNECTED; + 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; + + 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 @@ 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 @@ [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 @@ /* 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 @@ 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 @@ 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 @@ 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 49ec848..a448126 100644 --- a/src/osmo-bsc/gsm_04_08_rr.c +++ b/src/osmo-bsc/gsm_04_08_rr.c @@ -1027,7 +1027,6 @@ * 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 d1992ef..81d7a82 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> @@ -494,6 +497,10 @@ 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 fbc2ae2..1152783 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 8e231e0..573f249 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 @@ 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 0000000..27ef58b --- /dev/null +++ b/src/osmo-bsc/lb.c @@ -0,0 +1,657 @@ +/* Lb interface low level SCCP handling */ +/* + * (C) 2020 by sysmocom - s.f.m.c. GmbH <info at sysmocom.de> + * + * All Rights Reserved + * + * Author: Neels Hofmeyr <neels at 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 +{ + struct osmo_ss7_instance *ss7; + struct msgb *msg; + struct osmo_bssap_le_pdu reset = { + .discr = OSMO_BSSAP_LE_MSG_DISCR_BSSMAP_LE, + .bssmap_le = { + .msg_type = OSMO_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 osmo_bssap_le_pdu reset_ack = { + .discr = OSMO_BSSAP_LE_MSG_DISCR_BSSMAP_LE, + .bssmap_le = { + .msg_type = OSMO_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 osmo_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 osmo_bssap_le_pdu bssap_le; + const char *errmsg; + 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; + } + + errmsg = osmo_bssap_le_dec(&bssap_le, msg); + if (errmsg) { + LOGP(DLCS, LOGL_ERROR, "Rx BSSAP-LE UnitData with error: %s\n", errmsg); + rate_ctr_inc(&ctr[SMLC_CTR_BSSMAP_LE_RX_UDT_ERR_INVALID_MSG]); + return -EINVAL; + } + + if (bssap_le.discr != OSMO_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 OSMO_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 OSMO_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 osmo_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 OSMO_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 OSMO_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 OSMO_BSSMAP_LE_MSGT_CONN_ORIENTED_INFO: + switch (bssap_le->bssmap_le.conn_oriented_info.apdu.msg_type) { + case OSMO_BSSLAP_MSGT_TA_RESPONSE: + rate_ctr_inc(&bsc_gsmnet->smlc.ctrs->ctr[SMLC_CTR_BSSMAP_LE_TX_DT1_BSSLAP_TA_RESPONSE]); + break; + case OSMO_BSSLAP_MSGT_REJECT: + rate_ctr_inc(&bsc_gsmnet->smlc.ctrs->ctr[SMLC_CTR_BSSMAP_LE_TX_DT1_BSSLAP_REJECT]); + break; + case OSMO_BSSLAP_MSGT_RESET: + rate_ctr_inc(&bsc_gsmnet->smlc.ctrs->ctr[SMLC_CTR_BSSMAP_LE_TX_DT1_BSSLAP_RESET]); + break; + case OSMO_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.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.c b/src/osmo-bsc/lcs.c new file mode 100644 index 0000000..5fcde72 --- /dev/null +++ b/src/osmo-bsc/lcs.c @@ -0,0 +1,142 @@ +/* Location Services (LCS) handling */ +/* + * (C) 2020 by sysmocom - s.f.m.c. GmbH <info at sysmocom.de> + * All Rights Reserved + * + * Author: Neels Hofmeyr <neels at 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.h> + +void lcs_start_for_idle_ms(struct gsm_subscriber_connection *conn, struct msgb *ho_request_msg) +{ + struct lchan_activate_info info; + struct handover *ho = &conn->ho; + struct bsc_msc_data *msc = conn->sccp.msc; + struct handover_in_req *req = &ho->inter_bsc_in; + int match_idx; + struct osmo_fsm_inst *fi; + struct channel_mode_and_rate ch_mode_rate = {}; + + handover_fsm_alloc(conn); + + *ho = (struct handover){ + .fi = ho->fi, + .from_hodec_id = HODEC_REMOTE, + .scope = HO_INTER_BSC_IN, + .ho_ref = g_next_ho_ref++, + .async = true, + }; + + fi = ho->fi; + ho_fsm_update_id(fi, "interBSCin"); + + if (!parse_ho_request(conn, ho_request_msg, req)) { + ho_fail(HO_RESULT_ERROR, "Invalid Handover Request message from MSC\n"); + return; + } + + /* Figure out which cell to handover to. */ + for (match_idx = 0; ; match_idx++) { + struct gsm_bts *bts; + struct gsm_lchan *lchan; + + bts = gsm_bts_by_cell_id(conn->network, &req->cell_id_target, + match_idx); + + /* Did we iterate all matches? */ + if (!bts) + break; + + LOG_HO(conn, LOGL_DEBUG, "BTS %u matches cell id %s\n", + bts->nr, req->cell_id_target_name); + + /* Figure out channel type */ + if (match_codec_pref(&ch_mode_rate, &req->ct, &req->scl, msc, bts, RATE_PREF_NONE)) { + LOG_HO(conn, LOGL_DEBUG, + "BTS %u has no matching channel codec (%s, speech codec list len = %u)\n", + bts->nr, gsm0808_channel_type_name(&req->ct), req->scl.len); + continue; + } + + LOG_HO(conn, LOGL_DEBUG, "BTS %u: Found matching audio type: %s %s (for %s)\n", + bts->nr, gsm48_chan_mode_name(ch_mode_rate.chan_mode), + ch_mode_rate.chan_rate == CH_RATE_FULL ? "full-rate" : "half-rate", + gsm0808_channel_type_name(&req->ct)); + + lchan = lchan_select_by_chan_mode(bts, ch_mode_rate.chan_mode, ch_mode_rate.chan_rate); + if (!lchan) { + LOG_HO(conn, LOGL_DEBUG, "BTS %u has no matching free channels\n", bts->nr); + continue; + } + + /* Found a match. */ + ho->new_bts = bts; + ho->new_lchan = lchan; + break; + } + + ho_count(ho->new_bts, CTR_HANDOVER_ATTEMPTED); + ho_count(ho->new_bts, CTR_INTER_BSC_HO_IN_ATTEMPTED); + + if (!ho->new_bts) { + ho_fail(HO_RESULT_ERROR, "No local cell matches the target %s", + req->cell_id_target_name); + return; + } + + if (!ho->new_lchan) { + ho_fail(HO_RESULT_ERROR, "No free/matching lchan found for %s %s (speech codec list len = %u)", + req->cell_id_target_name, gsm0808_channel_type_name(&req->ct), req->scl.len); + return; + } + + /* Just for completeness' sake, maybe some logging uses it? */ + ho->new_lchan_type = ho->new_lchan->type; + + ho_fsm_state_chg(HO_ST_WAIT_LCHAN_ACTIVE); + + info = (struct lchan_activate_info){ + .activ_for = FOR_HANDOVER, + .for_conn = conn, + .chan_mode = ch_mode_rate.chan_mode, + .s15_s0 = ch_mode_rate.s15_s0, + .requires_voice_stream = chan_mode_is_tch(ch_mode_rate.chan_mode), + .msc_assigned_cic = req->msc_assigned_cic, + }; + + if (req->chosen_encr_alg) { + info.encr.alg_id = req->chosen_encr_alg; + if (info.encr.alg_id > 1 && !req->ei.key_len) { + ho_fail(HO_RESULT_ERROR, "Chosen Encryption Algorithm (Serving) reflects A5/%u" + " but there is no key (Encryption Information)", info.encr.alg_id - 1); + return; + } + } + + if (req->ei.key_len) { + if (req->ei.key_len > sizeof(info.encr.key)) { + ho_fail(HO_RESULT_ERROR, "Encryption Information IE key length is too large: %u\n", + req->ei.key_len); + return; + } + memcpy(info.encr.key, req->ei.key, req->ei.key_len); + info.encr.key_len = req->ei.key_len; + } + + lchan_activate(ho->new_lchan, &info); +} diff --git a/src/osmo-bsc/lcs_loc_req.c b/src/osmo-bsc/lcs_loc_req.c new file mode 100644 index 0000000..3a1c52a --- /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 at sysmocom.de> + * All Rights Reserved + * + * Author: Neels Hofmeyr <neels at 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + + +#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/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", \ + osmo_fsm_inst_state_name(lcs_loc_req->fi), ## args); \ + lcs_loc_req->lcs_cause = (struct osmo_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(OSMO_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, 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 = (struct lcs_loc_req){ + .fi = lcs_loc_req->fi, + .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(OSMO_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(OSMO_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 osmo_bssmap_le_pdu *bssmap_le) +{ + switch (bssmap_le->conn_oriented_info.apdu.msg_type) { + case OSMO_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 osmo_bssap_le_pdu bssap_le; + struct rate_ctr *ctr = bsc_gsmnet->smlc.ctrs->ctr; + const char *errmsg; + + if (!lcs_loc_req) { + LOGPFSMSL(conn->fi, DLCS, LOGL_ERROR, + "Rx BSSMAP-LE message, but no Location Request is ongoing\n"); + return -EINVAL; + } + + errmsg = osmo_bssap_le_dec(&bssap_le, msg); + + if (errmsg) { + LOG_LCS_LOC_REQ(lcs_loc_req, LOGL_ERROR, "Rx BSSAP-LE message with error: %s\n", errmsg); + rate_ctr_inc(&ctr[SMLC_CTR_BSSMAP_LE_RX_DT1_ERR_INVALID_MSG]); + return -EINVAL; + } + + if (bssap_le.discr != OSMO_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 OSMO_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 OSMO_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(OSMO_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(OSMO_LCS_CAUSE_SYSTEM_FAILURE, "Timeout"); + return 1; +} + +static int lcs_loc_req_send(struct lcs_loc_req *lcs_loc_req, const struct osmo_bssap_le_pdu *bssap_le) +{ + int rc = lb_send(lcs_loc_req->conn, bssap_le); + if (rc) + lcs_loc_req_fail(OSMO_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 osmo_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(OSMO_LCS_CAUSE_PROTOCOL_ERROR, + "Cannot encode BSSMAP-LE Perform Location Request," + " because mandatory Cell Identity is not known"); + return; + } + + plr = (struct osmo_bssap_le_pdu){ + .discr = OSMO_BSSAP_LE_MSG_DISCR_BSSMAP_LE, + .bssmap_le = { + .msg_type = OSMO_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 osmo_bsslap_pdu){ + .msg_type = OSMO_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 osmo_bssap_le_pdu pla = { + .discr = OSMO_BSSAP_LE_MSG_DISCR_BSSMAP_LE, + .bssmap_le = { + .msg_type = OSMO_BSSMAP_LE_MSGT_PERFORM_LOC_ABORT, + .perform_loc_abort = { + .present = true, + .cause_val = OSMO_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 osmo_bssap_le_pdu bsslap = { + .discr = OSMO_BSSAP_LE_MSG_DISCR_BSSMAP_LE, + .bssmap_le = { + .msg_type = OSMO_BSSMAP_LE_MSGT_CONN_ORIENTED_INFO, + }, + }; + struct osmo_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 osmo_bsslap_pdu){ + .msg_type = OSMO_BSSLAP_MSGT_ABORT, + .abort = OSMO_BSSLAP_CAUSE_INTER_BSS_HO, + }; + } else { + *apdu = (struct osmo_bsslap_pdu){ + .msg_type = OSMO_BSSLAP_MSGT_RESET, + .reset = { + .cell_id = lchan->ts->trx->bts->cell_identity, + .ta = lchan->rqd_ta, + .cause = OSMO_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 osmo_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 == OSMO_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) { + LOG_LCS_LOC_REQ(lcs_loc_req, LOGL_INFO, + "Perform Location Response contains Location Estimate: GAD type %d\n", + plr.location_estimate.type); + } + + 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 osmo_bssap_le_pdu pla = { + .discr = OSMO_BSSAP_LE_MSG_DISCR_BSSMAP_LE, + .bssmap_le = { + .msg_type = OSMO_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 0000000..80b271e --- /dev/null +++ b/src/osmo-bsc/lcs_ta_req.c @@ -0,0 +1,308 @@ +/* Handle LCS BSSLAP TA Request */ +/* + * (C) 2020 by sysmocom - s.f.m.c. GmbH <info at sysmocom.de> + * All Rights Reserved + * + * Author: Neels Hofmeyr <neels at 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#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", \ + osmo_fsm_inst_state_name(lcs_ta_req->fi), ## 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(OSMO_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(OSMO_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(OSMO_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 osmo_bssap_le_pdu *bssap_le) +{ + int rc = lb_send(lcs_ta_req->loc_req->conn, bssap_le); + if (rc) + lcs_ta_req_fail(OSMO_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 osmo_bssap_le_pdu bsslap_ta_resp; + struct gsm_lchan *lchan = lcs_ta_req->loc_req->conn->lchan; + + if (!lchan) { + lcs_ta_req_fail(OSMO_LCS_CAUSE_SYSTEM_FAILURE, "Internal error: no lchan"); + return; + } + + bsslap_ta_resp = (struct osmo_bssap_le_pdu) { + .discr = OSMO_BSSAP_LE_MSG_DISCR_BSSMAP_LE, + .bssmap_le = { + .msg_type = OSMO_BSSMAP_LE_MSGT_CONN_ORIENTED_INFO, + .conn_oriented_info = { + .apdu = { + .msg_type = OSMO_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 osmo_bssap_le_pdu bsslap_abort; + + bsslap_abort = (struct osmo_bssap_le_pdu) { + .discr = OSMO_BSSAP_LE_MSG_DISCR_BSSMAP_LE, + .bssmap_le = { + .msg_type = OSMO_BSSMAP_LE_MSGT_CONN_ORIENTED_INFO, + .conn_oriented_info = { + .apdu = { + .msg_type = OSMO_BSSLAP_MSGT_ABORT, + .abort = OSMO_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 8b1e080..6cbb40c 100644 --- a/src/osmo-bsc/net_init.c +++ b/src/osmo-bsc/net_init.c @@ -52,6 +52,7 @@ { .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 124f613..f0873ea 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 @@ 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 20bd620..03b832a 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> @@ -775,8 +776,12 @@ .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) @@ -934,6 +939,7 @@ 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 26d2bc8..583b6ff 100644 --- a/src/osmo-bsc/osmo_bsc_msc.c +++ b/src/osmo-bsc/osmo_bsc_msc.c @@ -62,6 +62,8 @@ [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 @@ [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 a4a5a1e..15aca00 100644 --- a/src/osmo-bsc/paging.c +++ b/src/osmo-bsc/paging.c @@ -79,9 +79,9 @@ 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 @@ 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) { diff --git a/tests/bsc/bsc_test.c b/tests/bsc/bsc_test.c index 6a6ff8b..0ed504b 100644 --- a/tests/bsc/bsc_test.c +++ b/tests/bsc/bsc_test.c @@ -30,6 +30,7 @@ #include <osmocom/bsc/osmo_bsc.h> #include <osmocom/bsc/bsc_msc_data.h> +#include <osmocom/gsm/gad.h> #include <osmocom/core/application.h> #include <osmocom/core/backtrace.h> #include <osmocom/core/talloc.h> diff --git a/tests/handover/Makefile.am b/tests/handover/Makefile.am index 87981d9..0f7953c 100644 --- a/tests/handover/Makefile.am +++ b/tests/handover/Makefile.am @@ -93,6 +93,10 @@ $(top_builddir)/src/osmo-bsc/smscb.o \ $(top_builddir)/src/osmo-bsc/cbch_scheduler.o \ $(top_builddir)/src/osmo-bsc/cbsp_link.o \ + $(top_builddir)/src/osmo-bsc/lcs_loc_req.o \ + $(top_builddir)/src/osmo-bsc/lcs_ta_req.o \ + $(top_builddir)/src/osmo-bsc/lb.o \ + $(top_builddir)/src/osmo-bsc/bsc_sccp.o \ $(LIBOSMOCORE_LIBS) \ $(LIBOSMOGSM_LIBS) \ $(LIBOSMOCTRL_LIBS) \ -- To view, visit https://gerrit.osmocom.org/c/osmo-bsc/+/20357 To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings Gerrit-Project: osmo-bsc Gerrit-Branch: master Gerrit-Change-Id: I28314ba97df86a118497e9b2770e2e6e2484e872 Gerrit-Change-Number: 20357 Gerrit-PatchSet: 1 Gerrit-Owner: neels <nhofmeyr at sysmocom.de> Gerrit-CC: Jenkins Builder Gerrit-MessageType: newchange -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://lists.osmocom.org/pipermail/gerrit-log/attachments/20201001/c3afb616/attachment.htm>