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-smlc/+/20470 ) Change subject: initial working osmo-smlc implementation ...................................................................... initial working osmo-smlc implementation The lower level Lb/SCCP interface conn handling is essentially a copy of OsmoMSC's A/SCCP infrastructure (OsmoMSC also connects to multiple BSCs). The smlc_subscr is mostly a copy of OsmoBSC's bsc_subscr. smlc_loc_req FSM is the SMLC side of OsmoBSC's new lcs_loc_req FSM. cell_locations configures geographic coordinates of individual cells. Change-Id: I917ba8fc51a1f1150be77ae01e12a7b16a853052 --- M configure.ac M doc/examples/osmo-smlc/osmo-smlc.cfg M include/osmocom/smlc/Makefile.am A include/osmocom/smlc/cell_locations.h A include/osmocom/smlc/debug.h A include/osmocom/smlc/lb_conn.h A include/osmocom/smlc/lb_peer.h A include/osmocom/smlc/sccp_lb_inst.h M include/osmocom/smlc/smlc_data.h A include/osmocom/smlc/smlc_loc_req.h M include/osmocom/smlc/smlc_sigtran.h A include/osmocom/smlc/smlc_subscr.h A include/osmocom/smlc/smlc_vty.h M src/osmo-smlc/Makefile.am A src/osmo-smlc/cell_locations.c A src/osmo-smlc/lb_conn.c A src/osmo-smlc/lb_peer.c A src/osmo-smlc/lcs_loc_req.c A src/osmo-smlc/sccp_lb_inst.c A src/osmo-smlc/smlc_data.c A src/osmo-smlc/smlc_loc_req.c M src/osmo-smlc/smlc_main.c D src/osmo-smlc/smlc_sigtran.c A src/osmo-smlc/smlc_subscr.c M tests/Makefile.am A tests/cell_locations.vty A tests/osmo-smlc.cfg A tests/smlc_subscr/Makefile.am A tests/smlc_subscr/smlc_subscr_test.c A tests/smlc_subscr/smlc_subscr_test.err A tests/smlc_subscr/smlc_subscr_test.ok M tests/testsuite.at 32 files changed, 2,884 insertions(+), 113 deletions(-) git pull ssh://gerrit.osmocom.org:29418/osmo-smlc refs/changes/70/20470/1 diff --git a/configure.ac b/configure.ac index e9c4ffc..186721b 100644 --- a/configure.ac +++ b/configure.ac @@ -212,6 +212,7 @@ src/osmo-smlc/Makefile tests/Makefile tests/atlocal + tests/smlc_subscr/Makefile doc/Makefile doc/examples/Makefile doc/manuals/Makefile diff --git a/doc/examples/osmo-smlc/osmo-smlc.cfg b/doc/examples/osmo-smlc/osmo-smlc.cfg index e69de29..6585d47 100644 --- a/doc/examples/osmo-smlc/osmo-smlc.cfg +++ b/doc/examples/osmo-smlc/osmo-smlc.cfg @@ -0,0 +1,3 @@ +cells + lac-ci 23 42 lat 12.34567 lon 34.56789 + cgi 262 42 17 5 lat 12.34765 lon 34.56987 diff --git a/include/osmocom/smlc/Makefile.am b/include/osmocom/smlc/Makefile.am index 599651d..cb25271 100644 --- a/include/osmocom/smlc/Makefile.am +++ b/include/osmocom/smlc/Makefile.am @@ -1,4 +1,11 @@ noinst_HEADERS = \ + cell_locations.h \ + debug.h \ + lb_conn.h \ + lb_peer.h \ + sccp_lb_inst.h \ smlc_data.h \ + smlc_loc_req.h \ smlc_sigtran.h \ + smlc_subscr.h \ $(NULL) diff --git a/include/osmocom/smlc/cell_locations.h b/include/osmocom/smlc/cell_locations.h new file mode 100644 index 0000000..33023bb --- /dev/null +++ b/include/osmocom/smlc/cell_locations.h @@ -0,0 +1,49 @@ +/* OsmoSMLC cell locations configuration */ +/* + * (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. + * + */ + +#pragma once + +#include <stdint.h> +#include <osmocom/core/linuxlist.h> +#include <osmocom/sigtran/sccp_sap.h> + +struct osmo_gad; + +struct cell_location { + struct llist_head entry; + + struct gsm0808_cell_id cell_id; + + /*! latitude in micro degrees (degrees * 1e6) */ + int32_t lat; + /*! longitude in micro degrees (degrees * 1e6) */ + int32_t lon; +}; + +int cell_location_from_ta(struct osmo_gad *location_estimate, + const struct gsm0808_cell_id *cell_id, + uint8_t ta); + +int cell_locations_vty_init(); diff --git a/include/osmocom/smlc/debug.h b/include/osmocom/smlc/debug.h new file mode 100644 index 0000000..0c64323 --- /dev/null +++ b/include/osmocom/smlc/debug.h @@ -0,0 +1,13 @@ +#pragma once + +#define DEBUG +#include <osmocom/core/logging.h> + +/* Debug Areas of the code */ +enum { + DSMLC, + DREF, + DLB, + DLCS, + Debug_LastEntry, +}; diff --git a/include/osmocom/smlc/lb_conn.h b/include/osmocom/smlc/lb_conn.h new file mode 100644 index 0000000..5e9fd9c --- /dev/null +++ b/include/osmocom/smlc/lb_conn.h @@ -0,0 +1,56 @@ +#pragma once +/* SMLC Lb connection implementation */ + +#include <stdint.h> + +#include <osmocom/core/linuxlist.h> +#include <osmocom/smlc/smlc_subscr.h> + +struct lb_peer; +struct osmo_fsm_inst; +struct msgb; +struct bssmap_le_pdu; + +#define LOG_LB_CONN_SL(CONN, CAT, LEVEL, file, line, FMT, args...) \ + LOGPSRC(CAT, LEVEL, file, line, "Lb-%d %s %s: " FMT, (CONN) ? (CONN)->sccp_conn_id : 0, \ + ((CONN) && (CONN)->smlc_subscr) ? smlc_subscr_to_str_c(OTC_SELECT, (CONN)->smlc_subscr) : "no-subscr", \ + (CONN) ? osmo_use_count_to_str_c(OTC_SELECT, &(CONN)->use_count) : "-", \ + ##args) + +#define LOG_LB_CONN_S(CONN, CAT, LEVEL, FMT, args...) \ + LOG_LB_CONN_SL(CONN, CAT, LEVEL, NULL, 0, FMT, ##args) + +#define LOG_LB_CONN(CONN, LEVEL, FMT, args...) \ + LOG_LB_CONN_S(CONN, DLB, LEVEL, FMT, ##args) + +#define SMLC_SUBSCR_USE_LB_CONN "Lb-conn" + +struct lb_conn { + struct llist_head entry; + struct osmo_use_count use_count; + + struct lb_peer *lb_peer; + uint32_t sccp_conn_id; + + bool closing; + + struct smlc_subscr *smlc_subscr; + struct smlc_loc_req *smlc_loc_req; +}; + +#define lb_conn_get(lb_conn, use) \ + OSMO_ASSERT(osmo_use_count_get_put(&(lb_conn)->use_count, use, 1) == 0) +#define lb_conn_put(lb_conn, use) \ + OSMO_ASSERT(osmo_use_count_get_put(&(lb_conn)->use_count, use, -1) == 0) + +struct lb_conn *lb_conn_create_incoming(struct lb_peer *lb_peer, uint32_t sccp_conn_id, const char *use_token); +struct lb_conn *lb_conn_create_outgoing(struct lb_peer *lb_peer, const char *use_token); +struct lb_conn *lb_conn_find_by_smlc_subscr(struct smlc_subscr *smlc_subscr, const char *use_token); + +int lb_conn_down_l2_co(struct lb_conn *lb_conn, struct msgb *l3, bool initial); +void lb_conn_msc_role_gone(struct lb_conn *lb_conn, struct osmo_fsm_inst *msc_role); +void lb_conn_close(struct lb_conn *lb_conn); +void lb_conn_discard(struct lb_conn *lb_conn); + +int lb_conn_rx(struct lb_conn *lb_conn, struct msgb *msg, bool initial); +int lb_conn_send_bssmap_le(struct lb_conn *lb_conn, const struct bssmap_le_pdu *bssmap_le); diff --git a/include/osmocom/smlc/lb_peer.h b/include/osmocom/smlc/lb_peer.h new file mode 100644 index 0000000..b5ac6d6 --- /dev/null +++ b/include/osmocom/smlc/lb_peer.h @@ -0,0 +1,67 @@ +#pragma once + +#include <osmocom/core/linuxlist.h> +#include <osmocom/gsm/gsm0808.h> +#include <osmocom/sigtran/sccp_sap.h> + +#include <osmocom/smlc/debug.h> +#include <osmocom/smlc/lb_conn.h> + +struct vlr_subscr; +struct lb_conn; +struct neighbor_ident_entry; + +#define LOG_LB_PEER_CAT(LB_PEER, subsys, loglevel, fmt, args ...) \ + LOGPFSMSL((LB_PEER)? (LB_PEER)->fi : NULL, subsys, loglevel, fmt, ## args) + +#define LOG_LB_PEER(LB_PEER, loglevel, fmt, args ...) \ + LOG_LB_PEER_CAT(LB_PEER, DLB, loglevel, fmt, ## args) + +struct lb_peer { + struct llist_head entry; + struct osmo_fsm_inst *fi; + + struct sccp_lb_inst *sli; + struct osmo_sccp_addr peer_addr; +}; + +#define lb_peer_for_each_lb_conn(LB_CONN, LB_PEER) \ + llist_for_each_entry(LB_CONN, &(LB_PEER)->sli->lb_conns, entry) \ + if ((LB_CONN)->lb_peer == (LB_PEER)) + +#define lb_peer_for_each_lb_conn_safe(LB_CONN, LB_CONN_NEXT, LB_PEER) \ + llist_for_each_entry_safe(LB_CONN, LB_CONN_NEXT, &(LB_PEER)->sli->lb_conns, entry) \ + if ((LB_CONN)->lb_peer == (LB_PEER)) + +enum lb_peer_state { + LB_PEER_ST_WAIT_RX_RESET = 0, + LB_PEER_ST_WAIT_RX_RESET_ACK, + LB_PEER_ST_READY, + LB_PEER_ST_DISCARDING, +}; + +enum lb_peer_event { + LB_PEER_EV_MSG_UP_CL = 0, + LB_PEER_EV_MSG_UP_CO_INITIAL, + LB_PEER_EV_MSG_UP_CO, + LB_PEER_EV_MSG_DOWN_CL, + LB_PEER_EV_MSG_DOWN_CO_INITIAL, + LB_PEER_EV_MSG_DOWN_CO, + LB_PEER_EV_RX_RESET, + LB_PEER_EV_RX_RESET_ACK, + LB_PEER_EV_CONNECTION_SUCCESS, + LB_PEER_EV_CONNECTION_TIMEOUT, +}; + +struct lb_peer_ev_ctx { + uint32_t conn_id; + struct lb_conn *lb_conn; + struct msgb *msg; +}; + +struct lb_peer *lb_peer_find_or_create(struct sccp_lb_inst *sli, const struct osmo_sccp_addr *peer_addr); +struct lb_peer *lb_peer_find(struct sccp_lb_inst *sli, const struct osmo_sccp_addr *peer_addr); + +int lb_peer_up_l2(struct sccp_lb_inst *sli, const struct osmo_sccp_addr *calling_addr, bool co, uint32_t conn_id, + struct msgb *l2); +void lb_peer_disconnect(struct sccp_lb_inst *sli, uint32_t conn_id); diff --git a/include/osmocom/smlc/sccp_lb_inst.h b/include/osmocom/smlc/sccp_lb_inst.h new file mode 100644 index 0000000..c662538 --- /dev/null +++ b/include/osmocom/smlc/sccp_lb_inst.h @@ -0,0 +1,68 @@ +/* Lb: BSSAP-LE/SCCP */ + +#pragma once + +#include <stdint.h> + +#include <osmocom/core/tdef.h> +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/gsm/gsm0808_utils.h> +#include <osmocom/sigtran/sccp_sap.h> + +struct msgb; +struct sccp_lb_inst; + +#define LOG_SCCP_LB_CO(sli, peer_addr, conn_id, level, fmt, args...) \ + LOGP(DLB, level, "(Lb-%u%s%s) " fmt, \ + conn_id, peer_addr ? " from " : "", \ + peer_addr ? osmo_sccp_inst_addr_name((sli)->sccp, peer_addr) : "", \ + ## args) + +#define LOG_SCCP_LB_CL_CAT(sli, peer_addr, subsys, level, fmt, args...) \ + LOGP(subsys, level, "(Lb%s%s) " fmt, \ + peer_addr ? " from " : "", \ + peer_addr ? osmo_sccp_inst_addr_name((sli)->sccp, peer_addr) : "", \ + ## args) + +#define LOG_SCCP_LB_CL(sli, peer_addr, level, fmt, args...) \ + LOG_SCCP_LB_CL_CAT(sli, peer_addr, DLB, level, fmt, ##args) + +#define LOG_SCCP_LB_CAT(sli, subsys, level, fmt, args...) \ + LOG_SCCP_LB_CL_CAT(sli, NULL, subsys, level, fmt, ##args) + +#define LOG_SCCP_LB(sli, level, fmt, args...) \ + LOG_SCCP_LB_CL(sli, NULL, level, fmt, ##args) + +enum reset_msg_type { + SCCP_LB_MSG_NON_RESET = 0, + SCCP_LB_MSG_RESET, + SCCP_LB_MSG_RESET_ACK, +}; + +struct sccp_lb_inst { + struct osmo_sccp_instance *sccp; + struct osmo_sccp_user *scu; + struct osmo_sccp_addr local_sccp_addr; + + struct llist_head lb_peers; + struct llist_head lb_conns; + + void *user_data; + + /* Compatibility with legacy osmo-hnbgw that was unable to properly handle RESET messages. Set to 'false' to + * require proper RESET procedures, set to 'true' to implicitly put a lb_peer in RAN_PEER_ST_READY upon the + * first CO message. Default is false = be strict. */ + bool ignore_missing_reset; +}; + +struct sccp_lb_inst *sccp_lb_init(void *talloc_ctx, struct osmo_sccp_instance *sccp, enum osmo_sccp_ssn ssn, + const char *sccp_user_name); +int sccp_lb_inst_next_conn_id(); + +int sccp_lb_down_l2_co_initial(struct sccp_lb_inst *sli, + const struct osmo_sccp_addr *called_addr, + uint32_t conn_id, struct msgb *l2); +int sccp_lb_down_l2_co(struct sccp_lb_inst *sli, uint32_t conn_id, struct msgb *l2); +int sccp_lb_down_l2_cl(struct sccp_lb_inst *sli, const struct osmo_sccp_addr *called_addr, struct msgb *l2); + +int sccp_lb_disconnect(struct sccp_lb_inst *sli, uint32_t conn_id, uint32_t cause); diff --git a/include/osmocom/smlc/smlc_data.h b/include/osmocom/smlc/smlc_data.h index 2506fdc..81ad242 100644 --- a/include/osmocom/smlc/smlc_data.h +++ b/include/osmocom/smlc/smlc_data.h @@ -11,22 +11,22 @@ #include <osmocom/sigtran/sccp_sap.h> struct smlc_state { - struct osmo_sccp_user *sccp_user; + struct osmo_sccp_instance *sccp_inst; + struct sccp_lb_inst *lb; + struct ctrl_handle *ctrl; struct rate_ctr_group *ctrs; struct osmo_stat_item_group *statg; - struct osmo_tdef *T_defs; + + struct llist_head subscribers; + struct llist_head cell_locations; }; extern struct smlc_state *g_smlc; +struct smlc_state *smlc_state_alloc(void *ctx); - -enum { - DSMLC, - DLB, /* Lb interface */ -}; - +extern struct osmo_tdef g_smlc_tdefs[]; int smlc_ctrl_node_lookup(void *data, vector vline, int *node_type, void **node_data, int *i); @@ -35,3 +35,25 @@ CTRL_NODE_SMLC = _LAST_CTRL_NODE, _LAST_CTRL_NODE_SMLC }; + +enum { + 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_REQUEST, + SMLC_CTR_BSSMAP_LE_RX_DT1_BSSLAP_TA_RESPONSE, + SMLC_CTR_BSSMAP_LE_RX_DT1_BSSLAP_REJECT, + SMLC_CTR_BSSMAP_LE_RX_DT1_BSSLAP_RESET, + SMLC_CTR_BSSMAP_LE_RX_DT1_BSSLAP_ABORT, + + 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_RESPONSE, + SMLC_CTR_BSSMAP_LE_TX_DT1_BSSLAP_TA_REQUEST, +}; diff --git a/include/osmocom/smlc/smlc_loc_req.h b/include/osmocom/smlc/smlc_loc_req.h new file mode 100644 index 0000000..a8aa27e --- /dev/null +++ b/include/osmocom/smlc/smlc_loc_req.h @@ -0,0 +1,65 @@ +/* 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. + * + */ +#pragma once + +#include <osmocom/smlc/debug.h> +#include <osmocom/gsm/bssmap_le.h> + +#define LOG_SMLC_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 smlc_ta_req; +struct lb_conn; +struct msgb; + +#define LB_CONN_USE_SMLC_LOC_REQ "smlc_loc_req" + +enum smlc_loc_req_fsm_event { + SMLC_LOC_REQ_EV_RX_TA_RESPONSE, + SMLC_LOC_REQ_EV_RX_BSSLAP_RESET, + SMLC_LOC_REQ_EV_RX_LE_PERFORM_LOCATION_ABORT, +}; + +struct smlc_loc_req { + struct osmo_fsm_inst *fi; + + struct smlc_subscr *smlc_subscr; + struct lb_conn *lb_conn; + + struct bssmap_le_perform_loc_req req; + + bool ta_present; + uint8_t ta; + + struct gsm0808_cell_id latest_cell_id; + + struct lcs_cause_ie lcs_cause; +}; + +int smlc_loc_req_rx_bssap_le(struct lb_conn *conn, const struct bssap_le_pdu *bssap_le); diff --git a/include/osmocom/smlc/smlc_sigtran.h b/include/osmocom/smlc/smlc_sigtran.h index aeaf2c5..f89f284 100644 --- a/include/osmocom/smlc/smlc_sigtran.h +++ b/include/osmocom/smlc/smlc_sigtran.h @@ -1,3 +1,5 @@ #pragma once int smlc_sigtran_init(void); +int smlc_sigtran_send(uint32_t sccp_conn_id, struct msgb *msg); +int smlc_sigtran_send_udt(uint32_t sccp_conn_id, struct msgb *msg); diff --git a/include/osmocom/smlc/smlc_subscr.h b/include/osmocom/smlc/smlc_subscr.h new file mode 100644 index 0000000..fbc4103 --- /dev/null +++ b/include/osmocom/smlc/smlc_subscr.h @@ -0,0 +1,30 @@ +#pragma once + +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/fsm.h> +#include <osmocom/core/use_count.h> +#include <osmocom/gsm/gsm48.h> +#include <osmocom/gsm/gsm0808.h> + +struct smlc_subscr { + struct llist_head entry; + struct osmo_use_count use_count; + + struct osmo_mobile_identity imsi; + struct gsm0808_cell_id cell_id; + + struct osmo_fsm_inst *loc_req; +}; + +struct smlc_subscr *smlc_subscr_find_or_create(const struct osmo_mobile_identity *imsi, const char *use_token); +struct smlc_subscr *smlc_subscr_find(const struct osmo_mobile_identity *imsi, const char *use_token); + +int smlc_subscr_to_str_buf(char *buf, size_t buf_len, const struct smlc_subscr *smlc_subscr); +char *smlc_subscr_to_str_c(void *ctx, const struct smlc_subscr *smlc_subscr); + +struct smlc_subscr *smlc_subscr_find_or_create(const struct osmo_mobile_identity *imsi, const char *use_token); + +#define smlc_subscr_get(smlc_subscr, use) \ + OSMO_ASSERT(osmo_use_count_get_put(&(smlc_subscr)->use_count, use, 1) == 0) +#define smlc_subscr_put(smlc_subscr, use) \ + OSMO_ASSERT(osmo_use_count_get_put(&(smlc_subscr)->use_count, use, -1) == 0) diff --git a/include/osmocom/smlc/smlc_vty.h b/include/osmocom/smlc/smlc_vty.h new file mode 100644 index 0000000..d5d82f8 --- /dev/null +++ b/include/osmocom/smlc/smlc_vty.h @@ -0,0 +1,7 @@ +#pragma once + +#include <osmocom/vty/command.h> + +enum smlc_vty_node { + CELLS_NODE = _LAST_OSMOVTY_NODE + 1, +}; diff --git a/src/osmo-smlc/Makefile.am b/src/osmo-smlc/Makefile.am index 269c23d..37fe042 100644 --- a/src/osmo-smlc/Makefile.am +++ b/src/osmo-smlc/Makefile.am @@ -23,9 +23,15 @@ $(NULL) osmo_smlc_SOURCES = \ + cell_locations.c \ + lb_conn.c \ + lb_peer.c \ + sccp_lb_inst.c \ smlc_ctrl.c \ + smlc_data.c \ + smlc_loc_req.c \ smlc_main.c \ - smlc_sigtran.c \ + smlc_subscr.c \ $(NULL) osmo_smlc_LDADD = \ diff --git a/src/osmo-smlc/cell_locations.c b/src/osmo-smlc/cell_locations.c new file mode 100644 index 0000000..2218a21 --- /dev/null +++ b/src/osmo-smlc/cell_locations.c @@ -0,0 +1,319 @@ +/* OsmoSMLC cell locations configuration */ +/* + * (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 <limits.h> +#include <inttypes.h> +#include <errno.h> + +#include <osmocom/core/utils.h> +#include <osmocom/gsm/protocol/gsm_08_08.h> +#include <osmocom/gsm/gsm0808_utils.h> +#include <osmocom/gsm/gad.h> +#include <osmocom/smlc/smlc_data.h> +#include <osmocom/smlc/smlc_vty.h> +#include <osmocom/smlc/cell_locations.h> + +static uint32_t ta_to_m(uint8_t ta) +{ + return ((uint32_t)ta) * 550; +} + +static struct cell_location *cell_location_find(const struct gsm0808_cell_id *cell_id) +{ + struct cell_location *cell_location; + llist_for_each_entry(cell_location, &g_smlc->cell_locations, entry) { + if (gsm0808_cell_ids_match(&cell_location->cell_id, cell_id, true)) + return cell_location; + } + llist_for_each_entry(cell_location, &g_smlc->cell_locations, entry) { + if (gsm0808_cell_ids_match(&cell_location->cell_id, cell_id, false)) + return cell_location; + } + return NULL; + +} + +int cell_location_from_ta(struct osmo_gad *location_estimate, + const struct gsm0808_cell_id *cell_id, + uint8_t ta) +{ + const struct cell_location *cell; + cell = cell_location_find(cell_id); + if (!cell) + return -ENOENT; + + *location_estimate = (struct osmo_gad){ + .type = GAD_TYPE_ELL_POINT_UNC_CIRCLE, + .ell_point_unc_circle = { + .lat = cell->lat, + .lon = cell->lon, + .unc = osmo_gad_dec_unc(osmo_gad_enc_unc(ta_to_m(ta) * 1000)), + }, + }; + + return 0; +} + +static struct cell_location *cell_location_find_or_create(const struct gsm0808_cell_id *cell_id) +{ + struct cell_location *cell_location = cell_location_find(cell_id); + if (!cell_location) { + cell_location = talloc_zero(g_smlc, struct cell_location); + OSMO_ASSERT(cell_location); + cell_location->cell_id = *cell_id; + llist_add_tail(&cell_location->entry, &g_smlc->cell_locations); + } + return cell_location; + +} + +static const struct cell_location *cell_location_set(const struct gsm0808_cell_id *cell_id, int32_t lat, int32_t lon) +{ + struct cell_location *cell_location = cell_location_find_or_create(cell_id); + cell_location->lat = lat; + cell_location->lon = lon; + return 0; +} + +static int cell_location_remove(const struct gsm0808_cell_id *cell_id) +{ + struct cell_location *cell_location = cell_location_find(cell_id); + if (!cell_location) + return -ENOENT; + llist_del(&cell_location->entry); + talloc_free(cell_location); + return 0; +} + +#define LAC_CI_PARAMS "lac-ci <0-65535> <0-65535>" +#define LAC_CI_DOC "Cell location by LAC and CI\n" "LAC\n" "CI\n" + +#define CGI_PARAMS "cgi <0-999> <0-999> <0-65535> <0-65535>" +#define CGI_DOC "Cell location by Cell-Global ID\n" "MCC\n" "MNC\n" "LAC\n" "CI\n" + +#define LAT_LON_PARAMS "lat LATITUDE lon LONGITUDE" +#define LAT_LON_DOC "Global latitute coordinate\n" "Latitude floating-point number, -90.0 (S) to 90.0 (N)\n" \ + "Global longitude coordinate\n" "Longitude as floating-point number, -180.0 (W) to 180.0 (E)\n" + +static int vty_parse_lac_ci(struct vty *vty, struct gsm0808_cell_id *dst, const char **argv) +{ + *dst = (struct gsm0808_cell_id){ + .id_discr = CELL_IDENT_LAC_AND_CI, + .id.lac_and_ci = { + .lac = atoi(argv[0]), + .ci = atoi(argv[1]), + }, + }; + return 0; +} + +static int vty_parse_cgi(struct vty *vty, struct gsm0808_cell_id *dst, const char **argv) +{ + *dst = (struct gsm0808_cell_id){ + .id_discr = CELL_IDENT_WHOLE_GLOBAL, + }; + struct osmo_cell_global_id *cgi = &dst->id.global; + const char *mcc = argv[0]; + const char *mnc = argv[1]; + const char *lac = argv[2]; + const char *ci = argv[3]; + + if (osmo_mcc_from_str(mcc, &cgi->lai.plmn.mcc)) { + vty_out(vty, "%% Error decoding MCC: %s%s", mcc, VTY_NEWLINE); + return -EINVAL; + } + + if (osmo_mnc_from_str(mnc, &cgi->lai.plmn.mnc, &cgi->lai.plmn.mnc_3_digits)) { + vty_out(vty, "%% Error decoding MNC: %s%s", mnc, VTY_NEWLINE); + return -EINVAL; + } + + cgi->lai.lac = atoi(lac); + cgi->cell_identity = atoi(ci); + return 0; +} + +static int vty_parse_location(struct vty *vty, const struct gsm0808_cell_id *cell_id, const char **argv) +{ + const char *lat_str = argv[0]; + const char *lon_str = argv[1]; + int64_t val; + int32_t lat, lon; + + if (osmo_float_str_to_int(&val, lat_str, 6) + || val < -90000000 || val > 90000000) { + vty_out(vty, "%% Invalid latitude: '%s'%s", lat_str, VTY_NEWLINE); + return CMD_WARNING; + } + lat = val; + + if (osmo_float_str_to_int(&val, lon_str, 6) + || val < -180000000 || val > 180000000) { + vty_out(vty, "%% Invalid longitude: '%s'%s", lon_str, VTY_NEWLINE); + return CMD_WARNING; + } + lon = val; + + if (cell_location_set(cell_id, lat, lon)) { + vty_out(vty, "%% Failed to add cell location%s", VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +DEFUN(cfg_cells, cfg_cells_cmd, + "cells", + "Configure cell locations\n") +{ + vty->node = CELLS_NODE; + return CMD_SUCCESS; +} + +DEFUN(cfg_cells_lac_ci, cfg_cells_lac_ci_cmd, + LAC_CI_PARAMS " " LAT_LON_PARAMS, + LAC_CI_DOC LAT_LON_DOC) +{ + struct gsm0808_cell_id cell_id; + + if (vty_parse_lac_ci(vty, &cell_id, argv)) + return CMD_WARNING; + + return vty_parse_location(vty, &cell_id, argv + 2); +} + +DEFUN(cfg_cells_no_lac_ci, cfg_cells_no_lac_ci_cmd, + "no " LAC_CI_PARAMS, + NO_STR "Remove " LAC_CI_DOC) +{ + struct gsm0808_cell_id cell_id; + + if (vty_parse_lac_ci(vty, &cell_id, argv)) + return CMD_WARNING; + if (cell_location_remove(&cell_id)) { + vty_out(vty, "%% cannot remove, no such entry%s", VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +DEFUN(cfg_cells_cgi, cfg_cells_cgi_cmd, + CGI_PARAMS " " LAT_LON_PARAMS, + CGI_DOC LAT_LON_DOC) +{ + struct gsm0808_cell_id cell_id; + + if (vty_parse_cgi(vty, &cell_id, argv)) + return CMD_WARNING; + + return vty_parse_location(vty, &cell_id, argv + 4); +} + +DEFUN(cfg_cells_no_cgi, cfg_cells_no_cgi_cmd, + "no " CGI_PARAMS, + NO_STR "Remove " CGI_DOC) +{ + struct gsm0808_cell_id cell_id; + + if (vty_parse_cgi(vty, &cell_id, argv)) + return CMD_WARNING; + if (cell_location_remove(&cell_id)) { + vty_out(vty, "%% cannot remove, no such entry%s", VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +/* The above are unidirectional cells. If we add configuration sector antennae, it would add arguments to the above, + * something like this: + * cgi 001 01 23 42 lat 23.23 lon 42.42 arc 270 30 + */ + +struct cmd_node cells_node = { + CELLS_NODE, + "%s(config-cells)# ", + 1, +}; + +static int config_write_cells(struct vty *vty) +{ + struct cell_location *cell; + const struct osmo_cell_global_id *cgi; + + if (llist_empty(&g_smlc->cell_locations)) + return 0; + + vty_out(vty, "cells%s", VTY_NEWLINE); + + llist_for_each_entry(cell, &g_smlc->cell_locations, entry) { + switch (cell->cell_id.id_discr) { + case CELL_IDENT_LAC_AND_CI: + vty_out(vty, " lac-ci %u %u", cell->cell_id.id.lac_and_ci.lac, cell->cell_id.id.lac_and_ci.ci); + break; + case CELL_IDENT_WHOLE_GLOBAL: + cgi = &cell->cell_id.id.global; + vty_out(vty, " cgi %s %s %u %u", + osmo_mcc_name(cgi->lai.plmn.mcc), + osmo_mnc_name(cgi->lai.plmn.mnc, cgi->lai.plmn.mnc_3_digits), + cgi->lai.lac, cgi->cell_identity); + break; + default: + vty_out(vty, " %% [unsupported cell id type: %d]", + cell->cell_id.id_discr); + break; + } + + vty_out(vty, " lat %s lon %s%s", + osmo_int_to_float_str_c(OTC_SELECT, cell->lat, 6), + osmo_int_to_float_str_c(OTC_SELECT, cell->lon, 6), + VTY_NEWLINE); + } + + return 0; +} + +DEFUN(ve_show_cells, ve_show_cells_cmd, + "show cells", + SHOW_STR "Show configured cell locations\n") +{ + if (llist_empty(&g_smlc->cell_locations)) { + vty_out(vty, "%% No cell locations are configured%s", VTY_NEWLINE); + return CMD_SUCCESS; + } + config_write_cells(vty); + return CMD_SUCCESS; +} + +int cell_locations_vty_init() +{ + install_element(CONFIG_NODE, &cfg_cells_cmd); + install_node(&cells_node, config_write_cells); + install_element(CELLS_NODE, &cfg_cells_lac_ci_cmd); + install_element(CELLS_NODE, &cfg_cells_no_lac_ci_cmd); + install_element(CELLS_NODE, &cfg_cells_cgi_cmd); + install_element(CELLS_NODE, &cfg_cells_no_cgi_cmd); + install_element_ve(&ve_show_cells_cmd); + + return 0; +} diff --git a/src/osmo-smlc/lb_conn.c b/src/osmo-smlc/lb_conn.c new file mode 100644 index 0000000..be5f400 --- /dev/null +++ b/src/osmo-smlc/lb_conn.c @@ -0,0 +1,204 @@ +/* SMLC Lb connection implementation */ + +/* + * (C) 2020 by sysmocom s.m.f.c. <info at sysmocom.de> + * All Rights Reserved + * + * Author: Neels Hofmeyr + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <errno.h> + +#include <osmocom/core/logging.h> +#include <osmocom/core/fsm.h> +#include <osmocom/core/signal.h> +#include <osmocom/gsm/bssmap_le.h> + +#include <osmocom/smlc/debug.h> +#include <osmocom/smlc/smlc_data.h> +#include <osmocom/smlc/sccp_lb_inst.h> +#include <osmocom/smlc/lb_peer.h> +#include <osmocom/smlc/lb_conn.h> +#include <osmocom/smlc/smlc_loc_req.h> + +static int lb_conn_use_cb(struct osmo_use_count_entry *e, int32_t old_use_count, const char *file, int line) +{ + struct lb_conn *lb_conn = e->use_count->talloc_object; + int32_t total; + int level; + + if (!e->use) + return -EINVAL; + + total = osmo_use_count_total(&lb_conn->use_count); + + if (total == 0 + || (total == 1 && old_use_count == 0 && e->count == 1)) + level = LOGL_INFO; + else + level = LOGL_DEBUG; + + LOG_LB_CONN_SL(lb_conn, DREF, level, file, line, "%s %s: now used by %s\n", + (e->count - old_use_count) > 0? "+" : "-", e->use, + osmo_use_count_to_str_c(OTC_SELECT, &lb_conn->use_count)); + + if (e->count < 0) + return -ERANGE; + + if (total == 0) + lb_conn_close(lb_conn); + return 0; +} + +static struct lb_conn *lb_conn_alloc(struct lb_peer *lb_peer, uint32_t sccp_conn_id, const char *use_token) +{ + struct lb_conn *lb_conn; + + lb_conn = talloc(lb_peer, struct lb_conn); + OSMO_ASSERT(lb_conn); + + *lb_conn = (struct lb_conn){ + .lb_peer = lb_peer, + .sccp_conn_id = sccp_conn_id, + /* + .use_count = { + .talloc_object = lb_conn, + .use_cb = lb_conn_use_cb, + }, + */ + }; + lb_conn->use_count = (struct osmo_use_count){ + .talloc_object = lb_conn, + .use_cb = lb_conn_use_cb, + }; + + llist_add(&lb_conn->entry, &lb_peer->sli->lb_conns); + lb_conn_get(lb_conn, use_token); + return lb_conn; +} + +struct lb_conn *lb_conn_create_incoming(struct lb_peer *lb_peer, uint32_t sccp_conn_id, const char *use_token) +{ + LOG_LB_PEER(lb_peer, LOGL_DEBUG, "Incoming lb_conn id: %u\n", sccp_conn_id); + return lb_conn_alloc(lb_peer, sccp_conn_id, use_token); +} + +struct lb_conn *lb_conn_create_outgoing(struct lb_peer *lb_peer, const char *use_token) +{ + int new_conn_id = sccp_lb_inst_next_conn_id(); + if (new_conn_id < 0) + return NULL; + LOG_LB_PEER(lb_peer, LOGL_DEBUG, "Outgoing lb_conn id: %u\n", new_conn_id); + return lb_conn_alloc(lb_peer, new_conn_id, use_token); +} + +struct lb_conn *lb_conn_find_by_smlc_subscr(struct smlc_subscr *smlc_subscr, const char *use_token) +{ + struct lb_conn *lb_conn; + llist_for_each_entry(lb_conn, &g_smlc->lb->lb_conns, entry) { + if (lb_conn->smlc_subscr == smlc_subscr) { + lb_conn_get(lb_conn, use_token); + return lb_conn; + } + } + return NULL; +} + +int lb_conn_down_l2_co(struct lb_conn *lb_conn, struct msgb *l3, bool initial) +{ + struct lb_peer_ev_ctx co = { + .conn_id = lb_conn->sccp_conn_id, + .lb_conn = lb_conn, + .msg = l3, + }; + if (!lb_conn->lb_peer) + return -EIO; + return osmo_fsm_inst_dispatch(lb_conn->lb_peer->fi, + initial ? LB_PEER_EV_MSG_DOWN_CO_INITIAL : LB_PEER_EV_MSG_DOWN_CO, + &co); +} + +int lb_conn_rx(struct lb_conn *lb_conn, struct msgb *msg, bool initial) +{ + struct bssap_le_pdu bssap_le; + struct osmo_bssap_le_err *err; + if (osmo_bssap_le_dec(&bssap_le, &err, msg, msg)) { + LOG_LB_CONN(lb_conn, LOGL_ERROR, "Rx BSSAP-LE with error: %s\n", err->logmsg); + return -EINVAL; + } + + return smlc_loc_req_rx_bssap_le(lb_conn, &bssap_le); +} + +int lb_conn_send_bssmap_le(struct lb_conn *lb_conn, const struct bssmap_le_pdu *bssmap_le) +{ + struct msgb *msg; + int rc; + struct bssap_le_pdu bssap_le = { + .discr = BSSAP_LE_MSG_DISCR_BSSMAP_LE, + .bssmap_le = *bssmap_le, + }; + + msg = osmo_bssap_le_enc(&bssap_le); + if (!msg) { + LOG_LB_CONN(lb_conn, LOGL_ERROR, "Unable to encode %s\n", + osmo_bssap_le_pdu_to_str_c(OTC_SELECT, &bssap_le)); + return -EINVAL; + } + + rc = sccp_lb_down_l2_co(lb_conn->lb_peer->sli, lb_conn->sccp_conn_id, msg); + if (rc) + LOG_LB_CONN(lb_conn, LOGL_ERROR, "Unable to send %s\n", + osmo_bssap_le_pdu_to_str_c(OTC_SELECT, &bssap_le)); + return rc; +} + +/* Regularly close the lb_conn */ +void lb_conn_close(struct lb_conn *lb_conn) +{ + if (!lb_conn) + return; + if (lb_conn->closing) + return; + lb_conn->closing = true; + LOG_LB_PEER(lb_conn->lb_peer, LOGL_DEBUG, "Closing lb_conn\n"); + + if (lb_conn->lb_peer) { + /* Todo: pass a useful SCCP cause? */ + sccp_lb_disconnect(lb_conn->lb_peer->sli, lb_conn->sccp_conn_id, 0); + lb_conn->lb_peer = NULL; + } + + if (lb_conn->smlc_loc_req) + osmo_fsm_inst_term(lb_conn->smlc_loc_req->fi, OSMO_FSM_TERM_REGULAR, NULL); + + if (lb_conn->smlc_subscr) + smlc_subscr_put(lb_conn->smlc_subscr, SMLC_SUBSCR_USE_LB_CONN); + + llist_del(&lb_conn->entry); + talloc_free(lb_conn); +} + +/* Same as lb_conn_close() but without sending any SCCP messages (e.g. after RESET) */ +void lb_conn_discard(struct lb_conn *lb_conn) +{ + if (!lb_conn) + return; + /* Make sure to drop dead and don't dispatch things like DISCONNECT requests on SCCP. */ + lb_conn->lb_peer = NULL; + lb_conn_close(lb_conn); +} diff --git a/src/osmo-smlc/lb_peer.c b/src/osmo-smlc/lb_peer.c new file mode 100644 index 0000000..038c8ac --- /dev/null +++ b/src/osmo-smlc/lb_peer.c @@ -0,0 +1,502 @@ +/* + * (C) 2019 by sysmocom - s.m.f.c. GmbH <info at sysmocom.de> + * All Rights Reserved + * + * SPDX-License-Identifier: AGPL-3.0+ + * + * Author: Neels Hofmeyr + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/logging.h> +#include <osmocom/core/fsm.h> +#include <osmocom/gsm/bssmap_le.h> +#include <osmocom/sigtran/sccp_helpers.h> + +#include <osmocom/smlc/smlc_data.h> +#include <osmocom/smlc/sccp_lb_inst.h> +#include <osmocom/smlc/lb_peer.h> + +static struct osmo_fsm lb_peer_fsm; + +static __attribute__((constructor)) void lb_peer_init() +{ + OSMO_ASSERT( osmo_fsm_register(&lb_peer_fsm) == 0); +} + +static struct lb_peer *lb_peer_alloc(struct sccp_lb_inst *sli, const struct osmo_sccp_addr *peer_addr) +{ + struct lb_peer *lbp; + struct osmo_fsm_inst *fi; + + fi = osmo_fsm_inst_alloc(&lb_peer_fsm, sli, NULL, LOGL_DEBUG, NULL); + OSMO_ASSERT(fi); + + osmo_fsm_inst_update_id(fi, osmo_sccp_addr_to_id_c(OTC_SELECT, osmo_sccp_get_ss7(sli->sccp), peer_addr)); + + lbp = talloc_zero(fi, struct lb_peer); + OSMO_ASSERT(lbp); + *lbp = (struct lb_peer){ + .fi = fi, + .sli = sli, + .peer_addr = *peer_addr, + }; + fi->priv = lbp; + + llist_add(&lbp->entry, &sli->lb_peers); + + return lbp; +} + +struct lb_peer *lb_peer_find_or_create(struct sccp_lb_inst *sli, const struct osmo_sccp_addr *peer_addr) +{ + struct lb_peer *lbp = lb_peer_find(sli, peer_addr); + if (lbp) + return lbp; + return lb_peer_alloc(sli, peer_addr); +} + +struct lb_peer *lb_peer_find(struct sccp_lb_inst *sli, const struct osmo_sccp_addr *peer_addr) +{ + struct lb_peer *lbp; + llist_for_each_entry(lbp, &sli->lb_peers, entry) { + if (osmo_sccp_addr_ri_cmp(peer_addr, &lbp->peer_addr)) + continue; + return lbp; + } + return NULL; +} + +static const struct osmo_tdef_state_timeout lb_peer_fsm_timeouts[32] = { + [LB_PEER_ST_WAIT_RX_RESET_ACK] = { .T = -13 }, + [LB_PEER_ST_DISCARDING] = { .T = -14 }, +}; + +#define lb_peer_state_chg(LB_PEER, NEXT_STATE) \ + osmo_tdef_fsm_inst_state_chg((LB_PEER)->fi, NEXT_STATE, lb_peer_fsm_timeouts, g_smlc_tdefs, 5) + +void lb_peer_discard_all_conns(struct lb_peer *lbp) +{ + struct lb_conn *lb_conn, *next; + + lb_peer_for_each_lb_conn_safe(lb_conn, next, lbp) { + lb_conn_discard(lb_conn); + } +} + +/* Drop all SCCP connections for this lb_peer, respond with RESET ACKNOWLEDGE and move to READY state. */ +static void lb_peer_rx_reset(struct lb_peer *lbp, struct msgb *msg) +{ + struct msgb *resp; + struct bssap_le_pdu reset_ack = { + .discr = BSSAP_LE_MSG_DISCR_BSSMAP_LE, + .bssmap_le = { + .msg_type = BSSMAP_LE_MSGT_RESET_ACK, + }, + }; + + lb_peer_discard_all_conns(lbp); + + resp = osmo_bssap_le_enc(&reset_ack); + if (!resp) { + LOG_LB_PEER(lbp, LOGL_ERROR, "Failed to compose RESET ACKNOWLEDGE message\n"); + lb_peer_state_chg(lbp, LB_PEER_ST_WAIT_RX_RESET); + return; + } + + if (sccp_lb_down_l2_cl(lbp->sli, &lbp->peer_addr, resp)) { + LOG_LB_PEER(lbp, LOGL_ERROR, "Failed to send RESET ACKNOWLEDGE message\n"); + lb_peer_state_chg(lbp, LB_PEER_ST_WAIT_RX_RESET); + msgb_free(msg); + return; + } + + LOG_LB_PEER(lbp, LOGL_INFO, "Sent RESET ACKNOWLEDGE\n"); + + /* sccp_lb_down_l2_cl() doesn't free msgb */ + msgb_free(resp); + + lb_peer_state_chg(lbp, LB_PEER_ST_READY); +} + +static void lb_peer_rx_reset_ack(struct lb_peer *lbp, struct msgb* msg) +{ + lb_peer_state_chg(lbp, LB_PEER_ST_READY); +} + +void lb_peer_reset(struct lb_peer *lbp) +{ + struct bssap_le_pdu reset = { + .discr = BSSAP_LE_MSG_DISCR_BSSMAP_LE, + .bssmap_le = { + .msg_type = BSSMAP_LE_MSGT_RESET, + .reset = GSM0808_CAUSE_EQUIPMENT_FAILURE, + }, + }; + struct msgb *msg; + + lb_peer_state_chg(lbp, LB_PEER_ST_WAIT_RX_RESET_ACK); + lb_peer_discard_all_conns(lbp); + + msg = osmo_bssap_le_enc(&reset); + if (!msg) { + LOG_LB_PEER(lbp, LOGL_ERROR, "Failed to compose RESET message\n"); + lb_peer_state_chg(lbp, LB_PEER_ST_WAIT_RX_RESET); + return; + } + + if (sccp_lb_down_l2_cl(lbp->sli, &lbp->peer_addr, msg)) { + LOG_LB_PEER(lbp, LOGL_ERROR, "Failed to send RESET message\n"); + lb_peer_state_chg(lbp, LB_PEER_ST_WAIT_RX_RESET); + return; + } +} + +void lb_peer_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct lb_peer *lbp = fi->priv; + struct lb_peer_ev_ctx *ctx = data; + struct msgb *msg = ctx->msg; + enum bssmap_le_msgt msg_type; + + switch (event) { + case LB_PEER_EV_MSG_UP_CL: + msg_type = osmo_bssmap_le_msgt(msgb_l2(msg), msgb_l2len(msg)); + switch (msg_type) { + case BSSMAP_LE_MSGT_RESET: + osmo_fsm_inst_dispatch(fi, LB_PEER_EV_RX_RESET, msg); + return; + case BSSMAP_LE_MSGT_RESET_ACK: + osmo_fsm_inst_dispatch(fi, LB_PEER_EV_RX_RESET_ACK, msg); + return; + default: + LOG_LB_PEER(lbp, LOGL_ERROR, "Unhandled ConnectionLess message received: %s\n", + osmo_bssmap_le_msgt_name(msg_type)); + return; + } + + default: + LOG_LB_PEER(lbp, LOGL_ERROR, "Unhandled event: %s\n", osmo_fsm_event_name(&lb_peer_fsm, event)); + return; + } +} + +void lb_peer_st_wait_rx_reset(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct lb_peer *lbp = fi->priv; + struct lb_peer_ev_ctx *ctx; + struct msgb *msg; + + switch (event) { + + case LB_PEER_EV_MSG_UP_CO: + case LB_PEER_EV_MSG_UP_CO_INITIAL: + ctx = data; + OSMO_ASSERT(ctx); + + if (lbp->sli->ignore_missing_reset) { + LOG_LB_PEER(lbp, LOGL_ERROR, "Receiving CO message on Lb peer that has not done a proper RESET yet." + " Accepting Lb peer implicitly (legacy compat)\n"); + lb_peer_state_chg(lbp, LB_PEER_ST_READY); + osmo_fsm_inst_dispatch(lbp->fi, event, data); + return; + } + + LOG_LB_PEER(lbp, LOGL_ERROR, "Receiving CO message on Lb peer that has not done a proper RESET yet." + " Disconnecting on incoming message, sending RESET to Lb peer.\n"); + /* No valid RESET procedure has happened here yet. Usually, we're expecting the Lb peer (BSC, + * RNC) to first send a RESET message before sending Connection Oriented messages. So if we're + * getting a CO message, likely we've just restarted or something. Send a RESET to the peer. */ + + lb_peer_disconnect(lbp->sli, ctx->conn_id); + + lb_peer_reset(lbp); + return; + + case LB_PEER_EV_RX_RESET: + msg = (struct msgb*)data; + lb_peer_rx_reset(lbp, msg); + return; + + default: + LOG_LB_PEER(lbp, LOGL_ERROR, "Unhandled event: %s\n", osmo_fsm_event_name(&lb_peer_fsm, event)); + return; + } +} + +void lb_peer_st_wait_rx_reset_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct lb_peer *lbp = fi->priv; + struct lb_peer_ev_ctx *ctx; + struct msgb *msg; + + switch (event) { + + case LB_PEER_EV_RX_RESET_ACK: + msg = (struct msgb*)data; + lb_peer_rx_reset_ack(lbp, msg); + return; + + case LB_PEER_EV_MSG_UP_CO: + case LB_PEER_EV_MSG_UP_CO_INITIAL: + ctx = data; + OSMO_ASSERT(ctx); + LOG_LB_PEER(lbp, LOGL_ERROR, "Receiving CO message on Lb peer that has not done a proper RESET yet." + " Disconnecting on incoming message, sending RESET to Lb peer.\n"); + sccp_lb_disconnect(lbp->sli, ctx->conn_id, 0); + /* No valid RESET procedure has happened here yet. */ + lb_peer_reset(lbp); + return; + + case LB_PEER_EV_RX_RESET: + msg = (struct msgb*)data; + lb_peer_rx_reset(lbp, msg); + return; + + default: + LOG_LB_PEER(lbp, LOGL_ERROR, "Unhandled event: %s\n", osmo_fsm_event_name(&lb_peer_fsm, event)); + return; + } +} + +void lb_peer_st_ready(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct lb_peer *lbp = fi->priv; + struct lb_peer_ev_ctx *ctx; + struct lb_conn *lb_conn; + struct msgb *msg; + + switch (event) { + + case LB_PEER_EV_MSG_UP_CO_INITIAL: + ctx = data; + OSMO_ASSERT(ctx); + OSMO_ASSERT(!ctx->lb_conn); + OSMO_ASSERT(ctx->msg); + + lb_conn = lb_conn_create_incoming(lbp, ctx->conn_id, __func__); + if (!lb_conn) { + LOG_LB_PEER(lbp, LOGL_ERROR, "Cannot allocate lb_conn\n"); + return; + } + + lb_conn_rx(lb_conn, ctx->msg, true); + lb_conn_put(lb_conn, __func__); + return; + + case LB_PEER_EV_MSG_UP_CO: + ctx = data; + OSMO_ASSERT(ctx); + OSMO_ASSERT(ctx->lb_conn); + OSMO_ASSERT(ctx->msg); + + lb_conn_rx(ctx->lb_conn, ctx->msg, false); + return; + + case LB_PEER_EV_MSG_DOWN_CO_INITIAL: + ctx = data; + OSMO_ASSERT(ctx); + OSMO_ASSERT(ctx->msg); + sccp_lb_down_l2_co_initial(lbp->sli, &lbp->peer_addr, ctx->conn_id, ctx->msg); + return; + + case LB_PEER_EV_MSG_DOWN_CO: + ctx = data; + OSMO_ASSERT(ctx); + OSMO_ASSERT(ctx->msg); + sccp_lb_down_l2_co(lbp->sli, ctx->conn_id, ctx->msg); + return; + + case LB_PEER_EV_MSG_DOWN_CL: + OSMO_ASSERT(data); + sccp_lb_down_l2_cl(lbp->sli, &lbp->peer_addr, (struct msgb*)data); + return; + + case LB_PEER_EV_RX_RESET: + msg = (struct msgb*)data; + lb_peer_rx_reset(lbp, msg); + return; + + default: + LOG_LB_PEER(lbp, LOGL_ERROR, "Unhandled event: %s\n", osmo_fsm_event_name(&lb_peer_fsm, event)); + return; + } +} + +static int lb_peer_fsm_timer_cb(struct osmo_fsm_inst *fi) +{ + struct lb_peer *lbp = fi->priv; + lb_peer_state_chg(lbp, LB_PEER_ST_WAIT_RX_RESET); + return 0; +} + +void lb_peer_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause) +{ + struct lb_peer *lbp = fi->priv; + lb_peer_discard_all_conns(lbp); + llist_del(&lbp->entry); +} + +static const struct value_string lb_peer_fsm_event_names[] = { + OSMO_VALUE_STRING(LB_PEER_EV_MSG_UP_CL), + OSMO_VALUE_STRING(LB_PEER_EV_MSG_UP_CO_INITIAL), + OSMO_VALUE_STRING(LB_PEER_EV_MSG_UP_CO), + OSMO_VALUE_STRING(LB_PEER_EV_MSG_DOWN_CL), + OSMO_VALUE_STRING(LB_PEER_EV_MSG_DOWN_CO_INITIAL), + OSMO_VALUE_STRING(LB_PEER_EV_MSG_DOWN_CO), + OSMO_VALUE_STRING(LB_PEER_EV_RX_RESET), + OSMO_VALUE_STRING(LB_PEER_EV_RX_RESET_ACK), + OSMO_VALUE_STRING(LB_PEER_EV_CONNECTION_SUCCESS), + OSMO_VALUE_STRING(LB_PEER_EV_CONNECTION_TIMEOUT), + {} +}; + +#define S(x) (1 << (x)) + +static const struct osmo_fsm_state lb_peer_fsm_states[] = { + [LB_PEER_ST_WAIT_RX_RESET] = { + .name = "WAIT_RX_RESET", + .action = lb_peer_st_wait_rx_reset, + .in_event_mask = 0 + | S(LB_PEER_EV_RX_RESET) + | S(LB_PEER_EV_MSG_UP_CO_INITIAL) + | S(LB_PEER_EV_MSG_UP_CO) + | S(LB_PEER_EV_CONNECTION_TIMEOUT) + , + .out_state_mask = 0 + | S(LB_PEER_ST_WAIT_RX_RESET) + | S(LB_PEER_ST_WAIT_RX_RESET_ACK) + | S(LB_PEER_ST_READY) + | S(LB_PEER_ST_DISCARDING) + , + }, + [LB_PEER_ST_WAIT_RX_RESET_ACK] = { + .name = "WAIT_RX_RESET_ACK", + .action = lb_peer_st_wait_rx_reset_ack, + .in_event_mask = 0 + | S(LB_PEER_EV_RX_RESET) + | S(LB_PEER_EV_RX_RESET_ACK) + | S(LB_PEER_EV_MSG_UP_CO_INITIAL) + | S(LB_PEER_EV_MSG_UP_CO) + | S(LB_PEER_EV_CONNECTION_TIMEOUT) + , + .out_state_mask = 0 + | S(LB_PEER_ST_WAIT_RX_RESET) + | S(LB_PEER_ST_WAIT_RX_RESET_ACK) + | S(LB_PEER_ST_READY) + | S(LB_PEER_ST_DISCARDING) + , + }, + [LB_PEER_ST_READY] = { + .name = "READY", + .action = lb_peer_st_ready, + .in_event_mask = 0 + | S(LB_PEER_EV_RX_RESET) + | S(LB_PEER_EV_MSG_UP_CO_INITIAL) + | S(LB_PEER_EV_MSG_UP_CO) + | S(LB_PEER_EV_MSG_DOWN_CO_INITIAL) + | S(LB_PEER_EV_MSG_DOWN_CO) + | S(LB_PEER_EV_MSG_DOWN_CL) + , + .out_state_mask = 0 + | S(LB_PEER_ST_WAIT_RX_RESET) + | S(LB_PEER_ST_WAIT_RX_RESET_ACK) + | S(LB_PEER_ST_READY) + | S(LB_PEER_ST_DISCARDING) + , + }, + [LB_PEER_ST_DISCARDING] = { + .name = "DISCARDING", + }, +}; + +static struct osmo_fsm lb_peer_fsm = { + .name = "lb_peer", + .states = lb_peer_fsm_states, + .num_states = ARRAY_SIZE(lb_peer_fsm_states), + .log_subsys = DLB, + .event_names = lb_peer_fsm_event_names, + .timer_cb = lb_peer_fsm_timer_cb, + .cleanup = lb_peer_fsm_cleanup, + .allstate_action = lb_peer_allstate_action, + .allstate_event_mask = 0 + | S(LB_PEER_EV_MSG_UP_CL) + , +}; + +int lb_peer_up_l2(struct sccp_lb_inst *sli, const struct osmo_sccp_addr *calling_addr, bool co, uint32_t conn_id, + struct msgb *l2) +{ + struct lb_peer *lb_peer = NULL; + uint32_t event; + struct lb_peer_ev_ctx ctx = { + .conn_id = conn_id, + .msg = l2, + }; + + if (co) { + struct lb_conn *lb_conn; + llist_for_each_entry(lb_conn, &sli->lb_conns, entry) { + if (lb_conn->sccp_conn_id == conn_id) { + lb_peer = lb_conn->lb_peer; + ctx.lb_conn = lb_conn; + break; + } + } + + if (lb_peer && calling_addr) { + LOG_SCCP_LB_CO(sli, calling_addr, conn_id, LOGL_ERROR, + "Connection-Oriented Initial message for already existing conn_id." + " Dropping message.\n"); + return -EINVAL; + } + + if (!lb_peer && !calling_addr) { + LOG_SCCP_LB_CO(sli, calling_addr, conn_id, LOGL_ERROR, + "Connection-Oriented non-Initial message for unknown conn_id %u." + " Dropping message.\n", conn_id); + return -EINVAL; + } + } + + if (calling_addr) { + lb_peer = lb_peer_find_or_create(sli, calling_addr); + if (!lb_peer) { + LOG_SCCP_LB_CL(sli, calling_addr, LOGL_ERROR, "Cannot register Lb peer\n"); + return -EIO; + } + } + + OSMO_ASSERT(lb_peer && lb_peer->fi); + + if (co) + event = calling_addr ? LB_PEER_EV_MSG_UP_CO_INITIAL : LB_PEER_EV_MSG_UP_CO; + else + event = LB_PEER_EV_MSG_UP_CL; + + return osmo_fsm_inst_dispatch(lb_peer->fi, event, &ctx); +} + +void lb_peer_disconnect(struct sccp_lb_inst *sli, uint32_t conn_id) +{ + struct lb_conn *lb_conn; + llist_for_each_entry(lb_conn, &sli->lb_conns, entry) { + if (lb_conn->sccp_conn_id == conn_id) { + lb_conn_discard(lb_conn); + return; + } + } +} diff --git a/src/osmo-smlc/lcs_loc_req.c b/src/osmo-smlc/lcs_loc_req.c new file mode 100644 index 0000000..9826818 --- /dev/null +++ b/src/osmo-smlc/lcs_loc_req.c @@ -0,0 +1,148 @@ + +#include <osmocom/core/utils.h> +#include <osmocom/core/fsm.h> + +enum lcs_loc_req_fsm_state { + LCS_LOC_REQ_ST_INIT, + LCS_LOC_REQ_ST_WAIT_LOCATION_RESPONSE, + LCS_LOC_REQ_ST_GOT_LOCATION_RESPONSE, +}; + +enum lcs_loc_req_fsm_event { + LCS_LOC_REQ_EV_START, + LCS_LOC_REQ_EV_RX_LE_PERFORM_LOCATION_RESPONSE, +}; + +static const struct value_string lcs_loc_req_fsm_event_names[] = { + OSMO_VALUE_STRING(LCS_LOC_REQ_EV_START), + OSMO_VALUE_STRING(LCS_LOC_REQ_EV_RX_LE_PERFORM_LOCATION_RESPONSE), + {} +}; + +static struct osmo_fsm lcs_loc_req_fsm; + +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 int lcs_loc_req_fsm_timer_cb(struct osmo_fsm_inst *fi) +{ + //struct lcs_loc_req *lcs_loc_req = fi->priv; + /* Return 1 to terminate FSM instance, 0 to keep running */ + return 1; +} + +static void lcs_loc_req_init_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + //struct lcs_loc_req *lcs_loc_req = fi->priv; + + switch (event) { + + case LCS_LOC_REQ_EV_START: + // FIXME + break; + + default: + OSMO_ASSERT(false); + } +} + +static void lcs_loc_req_wait_location_response_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + //struct lcs_loc_req *lcs_loc_req = fi->priv; + // FIXME +} + +static void lcs_loc_req_wait_location_response_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + //struct lcs_loc_req *lcs_loc_req = fi->priv; + + switch (event) { + + case LCS_LOC_REQ_EV_RX_LE_PERFORM_LOCATION_RESPONSE: + // FIXME + break; + + default: + OSMO_ASSERT(false); + } +} + +static void lcs_loc_req_got_location_response_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + //struct lcs_loc_req *lcs_loc_req = fi->priv; + // FIXME +} + +static void lcs_loc_req_got_location_response_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + //struct lcs_loc_req *lcs_loc_req = fi->priv; + + switch (event) { + + default: + OSMO_ASSERT(false); + } +} + +#define S(x) (1 << (x)) + +static const struct osmo_fsm_state lcs_loc_req_fsm_states[] = { + [LCS_LOC_REQ_ST_INIT] = { + .name = "init", + .in_event_mask = 0 + | S(LCS_LOC_REQ_EV_START) + , + .out_state_mask = 0 + | S(LCS_LOC_REQ_ST_WAIT_LOCATION_RESPONSE) + , + .action = lcs_loc_req_init_action, + }, + [LCS_LOC_REQ_ST_WAIT_LOCATION_RESPONSE] = { + .name = "wait_location_response", + .in_event_mask = 0 + | S(LCS_LOC_REQ_EV_RX_LE_PERFORM_LOCATION_RESPONSE) + , + .out_state_mask = 0 + | S(LCS_LOC_REQ_ST_GOT_LOCATION_RESPONSE) + , + .onenter = lcs_loc_req_wait_location_response_onenter, + .action = lcs_loc_req_wait_location_response_action, + }, + [LCS_LOC_REQ_ST_GOT_LOCATION_RESPONSE] = { + .name = "got_location_response", + .in_event_mask = 0 + , + .out_state_mask = 0 + , + .onenter = lcs_loc_req_got_location_response_onenter, + .action = lcs_loc_req_got_location_response_action, + }, +}; + +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 = DLGLOBAL, // FIXME + .event_names = lcs_loc_req_fsm_event_names, + .timer_cb = lcs_loc_req_fsm_timer_cb, +}; + +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-smlc/sccp_lb_inst.c b/src/osmo-smlc/sccp_lb_inst.c new file mode 100644 index 0000000..49c6657 --- /dev/null +++ b/src/osmo-smlc/sccp_lb_inst.c @@ -0,0 +1,253 @@ +/* + * (C) 2020 by sysmocom - s.m.f.c. GmbH <info at sysmocom.de> + * All Rights Reserved + * + * SPDX-License-Identifier: AGPL-3.0+ + * + * Author: Neels Hofmeyr + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <osmocom/core/logging.h> + +#include <osmocom/sccp/sccp_types.h> +#include <osmocom/sigtran/sccp_sap.h> +#include <osmocom/sigtran/sccp_helpers.h> + +#include <osmocom/smlc/debug.h> +#include <osmocom/smlc/smlc_data.h> +#include <osmocom/smlc/sccp_lb_inst.h> +#include <osmocom/smlc/lb_peer.h> + +/* We need an unused SCCP conn_id across all SCCP users. */ +int sccp_lb_inst_next_conn_id() +{ + static uint32_t next_id = 1; + int i; + + /* This looks really suboptimal, but in most cases the static next_id should indicate exactly the next unused + * conn_id, and we only iterate all conns once to make super sure that it is not already in use. */ + + for (i = 0; i < 0xFFFFFF; i++) { + struct lb_peer *lb_peer; + uint32_t conn_id = next_id; + bool conn_id_already_used = false; + next_id = (next_id + 1) & 0xffffff; + + llist_for_each_entry(lb_peer, &g_smlc->lb->lb_peers, entry) { + struct lb_conn *conn; + lb_peer_for_each_lb_conn(conn, lb_peer) { + if (conn_id == conn->sccp_conn_id) { + conn_id_already_used = true; + break; + } + } + if (conn_id_already_used) + break; + } + + if (!conn_id_already_used) + return conn_id; + } + return -1; +} + +static int sccp_lb_sap_up(struct osmo_prim_hdr *oph, void *_scu); + +struct sccp_lb_inst *sccp_lb_init(void *talloc_ctx, struct osmo_sccp_instance *sccp, enum osmo_sccp_ssn ssn, + const char *sccp_user_name) +{ + struct sccp_lb_inst *sli = talloc(talloc_ctx, struct sccp_lb_inst); + OSMO_ASSERT(sli); + *sli = (struct sccp_lb_inst){ + .sccp = sccp, + }; + + INIT_LLIST_HEAD(&sli->lb_peers); + INIT_LLIST_HEAD(&sli->lb_conns); + + osmo_sccp_local_addr_by_instance(&sli->local_sccp_addr, sccp, ssn); + sli->scu = osmo_sccp_user_bind(sccp, sccp_user_name, sccp_lb_sap_up, ssn); + osmo_sccp_user_set_priv(sli->scu, sli); + + return sli; +} + +static int sccp_lb_sap_up(struct osmo_prim_hdr *oph, void *_scu) +{ + struct osmo_sccp_user *scu = _scu; + struct sccp_lb_inst *sli = osmo_sccp_user_get_priv(scu); + struct osmo_scu_prim *prim = (struct osmo_scu_prim *) oph; + struct osmo_sccp_addr *my_addr; + struct osmo_sccp_addr *peer_addr; + uint32_t conn_id; + int rc; + + switch (OSMO_PRIM_HDR(oph)) { + case OSMO_PRIM(OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_INDICATION): + /* indication of new inbound connection request */ + conn_id = prim->u.connect.conn_id; + my_addr = &prim->u.connect.called_addr; + peer_addr = &prim->u.connect.calling_addr; + LOG_SCCP_LB_CO(sli, peer_addr, conn_id, LOGL_DEBUG, "%s(%s)\n", __func__, osmo_scu_prim_name(oph)); + + if (!msgb_l2(oph->msg) || msgb_l2len(oph->msg) == 0) { + LOG_SCCP_LB_CO(sli, peer_addr, conn_id, LOGL_NOTICE, "Received invalid N-CONNECT.ind\n"); + rc = -1; + break; + } + + if (osmo_sccp_addr_ri_cmp(&sli->local_sccp_addr, my_addr)) + LOG_SCCP_LB_CO(sli, peer_addr, conn_id, LOGL_ERROR, + "Rx N-CONNECT: Called address is %s != local address %s\n", + osmo_sccp_inst_addr_to_str_c(OTC_SELECT, sli->sccp, my_addr), + osmo_sccp_inst_addr_to_str_c(OTC_SELECT, sli->sccp, &sli->local_sccp_addr)); + + /* ensure the local SCCP socket is ACTIVE */ + osmo_sccp_tx_conn_resp(scu, conn_id, my_addr, NULL, 0); + + rc = lb_peer_up_l2(sli, peer_addr, true, conn_id, oph->msg); + if (rc) + osmo_sccp_tx_disconn(scu, conn_id, my_addr, SCCP_RETURN_CAUSE_UNQUALIFIED); + break; + + case OSMO_PRIM(OSMO_SCU_PRIM_N_DATA, PRIM_OP_INDICATION): + /* connection-oriented data received */ + conn_id = prim->u.data.conn_id; + LOG_SCCP_LB_CO(sli, NULL, conn_id, LOGL_DEBUG, "%s(%s)\n", __func__, osmo_scu_prim_name(oph)); + + rc = lb_peer_up_l2(sli, NULL, true, conn_id, oph->msg); + break; + + case OSMO_PRIM(OSMO_SCU_PRIM_N_DISCONNECT, PRIM_OP_INDICATION): + /* indication of disconnect */ + conn_id = prim->u.disconnect.conn_id; + LOG_SCCP_LB_CO(sli, NULL, conn_id, LOGL_DEBUG, "%s(%s)\n", __func__, osmo_scu_prim_name(oph)); + + /* If there is no L2 payload in the N-DISCONNECT, no need to dispatch up_l2(). */ + if (msgb_l2len(oph->msg)) + rc = lb_peer_up_l2(sli, NULL, true, conn_id, oph->msg); + else + rc = 0; + + /* Make sure the lb_conn is dropped. It might seem more optimal to combine the disconnect() into + * up_l2(), but since an up_l2() dispatch might already cause the lb_conn to be discarded for other + * reasons, a separate disconnect() with a separate conn_id lookup is actually necessary. */ + sccp_lb_disconnect(sli, conn_id, 0); + break; + + case OSMO_PRIM(OSMO_SCU_PRIM_N_UNITDATA, PRIM_OP_INDICATION): + /* connection-less data received */ + my_addr = &prim->u.unitdata.called_addr; + peer_addr = &prim->u.unitdata.calling_addr; + LOG_SCCP_LB_CL(sli, peer_addr, LOGL_DEBUG, "%s(%s)\n", __func__, osmo_scu_prim_name(oph)); + + if (osmo_sccp_addr_ri_cmp(&sli->local_sccp_addr, my_addr)) + LOG_SCCP_LB_CO(sli, peer_addr, conn_id, LOGL_ERROR, + "Rx N-UNITDATA: Called address is %s != local address %s\n", + osmo_sccp_inst_addr_to_str_c(OTC_SELECT, sli->sccp, my_addr), + osmo_sccp_inst_addr_to_str_c(OTC_SELECT, sli->sccp, &sli->local_sccp_addr)); + + rc = lb_peer_up_l2(sli, peer_addr, false, 0, oph->msg); + break; + + default: + LOG_SCCP_LB_CL(sli, NULL, LOGL_ERROR, "%s(%s) unsupported\n", __func__, osmo_scu_prim_name(oph)); + rc = -1; + break; + } + + msgb_free(oph->msg); + return rc; +} + +/* Push some padding if necessary to reach a multiple-of-eight offset to be msgb_push() an osmo_scu_prim that will then + * be 8-byte aligned. */ +static void msgb_pad_mod8(struct msgb *msg) +{ + uint8_t mod8 = (intptr_t)(msg->data) % 8; + if (mod8) + msgb_push(msg, mod8); +} + +static int sccp_lb_sap_down(struct sccp_lb_inst *sli, struct osmo_prim_hdr *oph) +{ + int rc; + if (!sli->scu) { + rate_ctr_inc(&g_smlc->ctrs->ctr[SMLC_CTR_BSSMAP_LE_TX_ERR_CONN_NOT_READY]); + return -EIO; + } + rc = osmo_sccp_user_sap_down_nofree(sli->scu, oph); + if (rc >= 0) + rate_ctr_inc(&g_smlc->ctrs->ctr[SMLC_CTR_BSSMAP_LE_TX_SUCCESS]); + else + rate_ctr_inc(&g_smlc->ctrs->ctr[SMLC_CTR_BSSMAP_LE_TX_ERR_SEND]); + return rc; +} + +int sccp_lb_down_l2_co_initial(struct sccp_lb_inst *sli, + const struct osmo_sccp_addr *called_addr, + uint32_t conn_id, struct msgb *l2) +{ + struct osmo_scu_prim *prim; + + l2->l2h = l2->data; + + msgb_pad_mod8(l2); + prim = (struct osmo_scu_prim *) msgb_push(l2, sizeof(*prim)); + prim->u.connect = (struct osmo_scu_connect_param){ + .called_addr = *called_addr, + .calling_addr = sli->local_sccp_addr, + .sccp_class = 2, + //.importance = ?, + .conn_id = conn_id, + }; + osmo_prim_init(&prim->oph, SCCP_SAP_USER, OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_REQUEST, l2); + return sccp_lb_sap_down(sli, &prim->oph); +} + +int sccp_lb_down_l2_co(struct sccp_lb_inst *sli, uint32_t conn_id, struct msgb *l2) +{ + struct osmo_scu_prim *prim; + + l2->l2h = l2->data; + + msgb_pad_mod8(l2); + prim = (struct osmo_scu_prim *) msgb_push(l2, sizeof(*prim)); + prim->u.data.conn_id = conn_id; + osmo_prim_init(&prim->oph, SCCP_SAP_USER, OSMO_SCU_PRIM_N_DATA, PRIM_OP_REQUEST, l2); + return sccp_lb_sap_down(sli, &prim->oph); +} + +int sccp_lb_down_l2_cl(struct sccp_lb_inst *sli, const struct osmo_sccp_addr *called_addr, struct msgb *l2) +{ + struct osmo_scu_prim *prim; + + l2->l2h = l2->data; + + msgb_pad_mod8(l2); + prim = (struct osmo_scu_prim *) msgb_push(l2, sizeof(*prim)); + prim->u.unitdata = (struct osmo_scu_unitdata_param){ + .called_addr = *called_addr, + .calling_addr = sli->local_sccp_addr, + }; + osmo_prim_init(&prim->oph, SCCP_SAP_USER, OSMO_SCU_PRIM_N_UNITDATA, PRIM_OP_REQUEST, l2); + return sccp_lb_sap_down(sli, &prim->oph); +} + +int sccp_lb_disconnect(struct sccp_lb_inst *sli, uint32_t conn_id, uint32_t cause) +{ + return osmo_sccp_tx_disconn(sli->scu, conn_id, NULL, cause); +} diff --git a/src/osmo-smlc/smlc_data.c b/src/osmo-smlc/smlc_data.c new file mode 100644 index 0000000..d51bceb --- /dev/null +++ b/src/osmo-smlc/smlc_data.c @@ -0,0 +1,65 @@ +/* (C) 2020 by Harald Welte <laforge at gnumonks.org> + * All Rights Reserved + * + * 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/lienses/>. + * + */ + +#include <osmocom/core/stats.h> +#include <osmocom/smlc/smlc_data.h> + +struct osmo_tdef g_smlc_tdefs[] = { + { .T=-12, .default_val=5, .desc="Timeout for BSSLAP TA Response from BSC" }, + {} +}; + +static const struct rate_ctr_desc smlc_ctr_description[] = { + [SMLC_CTR_BSSMAP_LE_RX_UDT_RESET] = { "bssmap_le:rx_udt_reset", "Rx BSSMAP-LE Reset" }, + [SMLC_CTR_BSSMAP_LE_RX_UDT_RESET_ACK] = { "bssmap_le:rx_udt_reset_ack", "Rx BSSMAP-LE Reset Acknowledge" }, + [SMLC_CTR_BSSMAP_LE_RX_UDT_ERR_INVALID_MSG] = { "bssmap_le:rx_udt_err_invalid_msg", "Receive invalid UnitData message" }, + [SMLC_CTR_BSSMAP_LE_RX_DT1_ERR_INVALID_MSG] = { "bssmap_le:rx_dt1_err_invalid_msg", "Receive invalid DirectTransfer1 message" }, + [SMLC_CTR_BSSMAP_LE_RX_DT1_PERFORM_LOCATION_REQUEST] = { "bssmap_le:rx_dt1_perform_location_request", "Receive Perform Location Request from BSC" }, + [SMLC_CTR_BSSMAP_LE_RX_DT1_BSSLAP_TA_RESPONSE] = { "bssmap_le:rx_dt1_bsslap_ta_response", "Receive BSSLAP TA Response from BSC" }, + [SMLC_CTR_BSSMAP_LE_RX_DT1_BSSLAP_REJECT] = { "bssmap_le:rx_dt1_bsslap_reject", "Rx BSSLAP Reject from BSC" }, + [SMLC_CTR_BSSMAP_LE_RX_DT1_BSSLAP_RESET] = { "bssmap_le:rx_dt1_bsslap_reset", "Rx BSSLAP Reset (handover) from BSC" }, + [SMLC_CTR_BSSMAP_LE_RX_DT1_BSSLAP_ABORT] = { "bssmap_le:rx_dt1_bsslap_abort", "Rx BSSLAP Abort from BSC" }, + + [SMLC_CTR_BSSMAP_LE_TX_ERR_INVALID_MSG] = { "bssmap_le:tx_err_invalid_msg", "BSSMAP-LE send error: invalid message" }, + [SMLC_CTR_BSSMAP_LE_TX_ERR_CONN_NOT_READY] = { "bssmap_le:tx_err_conn_not_ready", "BSSMAP-LE send error: conn not ready" }, + [SMLC_CTR_BSSMAP_LE_TX_ERR_SEND] = { "bssmap_le:tx_err_send", "BSSMAP-LE send error" }, + [SMLC_CTR_BSSMAP_LE_TX_SUCCESS] = { "bssmap_le:tx_success", "BSSMAP-LE send success" }, + + [SMLC_CTR_BSSMAP_LE_TX_UDT_RESET] = { "bssmap_le:tx_udt_reset", "Transmit UnitData Reset" }, + [SMLC_CTR_BSSMAP_LE_TX_UDT_RESET_ACK] = { "bssmap_le:tx_udt_reset_ack", "Transmit UnitData Reset Acknowledge" }, + [SMLC_CTR_BSSMAP_LE_TX_DT1_PERFORM_LOCATION_RESPONSE] = { "bssmap_le:tx_dt1_perform_location_response", "Tx Perform Location Response to BSC" }, + [SMLC_CTR_BSSMAP_LE_TX_DT1_BSSLAP_TA_REQUEST] = { "bssmap_le:tx_dt1_bsslap_ta_request", "Tx BSSLAP TA Request to BSC" }, +}; + +static const struct rate_ctr_group_desc smlc_ctrg_desc = { + "smlc", + "serving mobile location center", + OSMO_STATS_CLASS_GLOBAL, + ARRAY_SIZE(smlc_ctr_description), + smlc_ctr_description, +}; + +struct smlc_state *smlc_state_alloc(void *ctx) +{ + struct smlc_state *smlc = talloc_zero(ctx, struct smlc_state); + OSMO_ASSERT(smlc); + INIT_LLIST_HEAD(&smlc->subscribers); + INIT_LLIST_HEAD(&smlc->cell_locations); + smlc->ctrs = rate_ctr_group_alloc(smlc, &smlc_ctrg_desc, 0); + return smlc; +} diff --git a/src/osmo-smlc/smlc_loc_req.c b/src/osmo-smlc/smlc_loc_req.c new file mode 100644 index 0000000..c944c5b --- /dev/null +++ b/src/osmo-smlc/smlc_loc_req.c @@ -0,0 +1,439 @@ +/* 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/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", \ + osmo_fsm_inst_state_name(smlc_loc_req->fi), ## 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(&g_smlc->ctrs->ctr[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; + 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(&bssmap_le.perform_loc_resp.location_estimate, + &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; + } + + 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, &bssmap_le.perform_loc_resp.location_estimate)); + + 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); +} diff --git a/src/osmo-smlc/smlc_main.c b/src/osmo-smlc/smlc_main.c index 9f32441..105ced3 100644 --- a/src/osmo-smlc/smlc_main.c +++ b/src/osmo-smlc/smlc_main.c @@ -31,12 +31,15 @@ #include <osmocom/vty/ports.h> #include <osmocom/vty/logging.h> #include <osmocom/vty/command.h> +#include <osmocom/vty/misc.h> #include <osmocom/sigtran/xua_msg.h> #include <osmocom/sigtran/sccp_sap.h> +#include <osmocom/smlc/debug.h> #include <osmocom/smlc/smlc_data.h> -#include <osmocom/smlc/smlc_sigtran.h> +#include <osmocom/smlc/sccp_lb_inst.h> +#include <osmocom/smlc/cell_locations.h> #define _GNU_SOURCE #include <getopt.h> @@ -47,9 +50,12 @@ #include <time.h> #include <unistd.h> - #include "../../config.h" +#define DEFAULT_M3UA_LOCAL_IP "localhost" +#define DEFAULT_M3UA_REMOTE_IP "localhost" +#define SMLC_DEFAULT_PC "0.23.6" + static const char *config_file = "osmo-smlc.cfg"; static int daemonize = 0; static void *tall_smlc_ctx; @@ -167,6 +173,26 @@ } static const struct log_info_cat smlc_categories[] = { + [DSMLC] = { + .name = "DSMLC", + .description = "Serving Mobile Location Center", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DREF] = { + .name = "DREF", + .description = "Reference Counting", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DLB] = { + .name = "DLB", + .description = "Lb interface", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DLCS] = { + .name = "DLCS", + .description = "Location Services", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, }; const struct log_info log_info = { @@ -177,6 +203,7 @@ int main(int argc, char **argv) { int rc; + int default_pc; tall_smlc_ctx = talloc_named_const(NULL, 1, "osmo-smlc"); msgb_talloc_ctx_init(tall_smlc_ctx, 0); @@ -190,13 +217,14 @@ osmo_fsm_set_dealloc_ctx(OTC_SELECT); - g_smlc = talloc_zero(tall_smlc_ctx, struct smlc_state); - OSMO_ASSERT(g_smlc); + g_smlc = smlc_state_alloc(tall_smlc_ctx); /* This needs to precede handle_options() */ vty_init(&vty_info); - //smlc_vty_init(g_smlc); + logging_vty_add_cmds(); + osmo_talloc_vty_add_cmds(); ctrl_vty_init(tall_smlc_ctx); + cell_locations_vty_init(); /* Initialize SS7 */ OSMO_ASSERT(osmo_ss7_init() == 0); @@ -235,9 +263,20 @@ } */ - if (smlc_sigtran_init() != 0) { - LOGP(DLB, LOGL_ERROR, "Failed to initialize sigtran backhaul.\n"); - exit(1); + default_pc = osmo_ss7_pointcode_parse(NULL, SMLC_DEFAULT_PC); + OSMO_ASSERT(default_pc); + + g_smlc->sccp_inst = osmo_sccp_simple_client_on_ss7_id(g_smlc, 0, "Lb", default_pc, OSMO_SS7_ASP_PROT_M3UA, + 0, DEFAULT_M3UA_LOCAL_IP, 0, DEFAULT_M3UA_REMOTE_IP); + if (!g_smlc->sccp_inst) { + fprintf(stderr, "Setting up SCCP failed\n"); + return 1; + } + + g_smlc->lb = sccp_lb_init(g_smlc, g_smlc->sccp_inst, OSMO_SCCP_SSN_SMLC_BSSAP_LE, "OsmoSMLC-Lb"); + if (!g_smlc->lb) { + fprintf(stderr, "Setting up Lb receiver failed\n"); + return 1; } signal(SIGINT, &signal_handler); diff --git a/src/osmo-smlc/smlc_sigtran.c b/src/osmo-smlc/smlc_sigtran.c deleted file mode 100644 index 902df0c..0000000 --- a/src/osmo-smlc/smlc_sigtran.c +++ /dev/null @@ -1,94 +0,0 @@ -/* (C) 2020 by Harald Welte <laforge at gnumonks.org> - * All Rights Reserved - * - * 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/lienses/>. - * - */ - -#include <errno.h> - -#include <osmocom/core/utils.h> -#include <osmocom/core/logging.h> -#include <osmocom/core/fsm.h> -#include <osmocom/core/linuxlist.h> -#include <osmocom/sigtran/osmo_ss7.h> -#include <osmocom/sigtran/sccp_sap.h> -#include <osmocom/gsm/gsm0808.h> - -#include <osmocom/smlc/smlc_data.h> -#include <osmocom/smlc/smlc_sigtran.h> - - -#define DEFAULT_M3UA_REMOTE_IP "localhost" -#define DEFAULT_PC "0.23.6" - -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; - 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(DLB, "N-UNITDATA.ind(%s)\n", osmo_hexdump(msgb_l2(oph->msg), msgb_l2len(oph->msg))); - //rc = handle_unitdata_from_bsc(&scu_prim->u.unitdata.calling_addr, oph->msg, scu); - break; - case OSMO_PRIM(OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_INDICATION): - /* Handle inbound connections */ - DEBUGP(DLB, "N-CONNECT.ind(X->%u)\n", scu_prim->u.connect.conn_id); - break; - case OSMO_PRIM(OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_CONFIRM): - /* Handle outbound connection confirmation */ - DEBUGP(DLB, "N-CONNECT.cnf(%u, %s)\n", scu_prim->u.connect.conn_id, - osmo_hexdump(msgb_l2(oph->msg), msgb_l2len(oph->msg))); - break; - case OSMO_PRIM(OSMO_SCU_PRIM_N_DATA, PRIM_OP_INDICATION): - /* Handle incoming connection oriented data */ - DEBUGP(DLB, "N-DATA.ind(%u, %s)\n", scu_prim->u.data.conn_id, - osmo_hexdump(msgb_l2(oph->msg), msgb_l2len(oph->msg))); - - break; - case OSMO_PRIM(OSMO_SCU_PRIM_N_DISCONNECT, PRIM_OP_INDICATION): - DEBUGP(DLB, "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); - break; - default: - LOGP(DLB, 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; -} - -int smlc_sigtran_init(void) -{ - struct osmo_sccp_instance *sccp; - int default_pc = osmo_ss7_pointcode_parse(NULL, DEFAULT_PC); - - OSMO_ASSERT(default_pc); - - sccp = osmo_sccp_simple_client_on_ss7_id(g_smlc, 0, "Lb", default_pc, OSMO_SS7_ASP_PROT_M3UA, - 0, NULL, 0, DEFAULT_M3UA_REMOTE_IP); - - - g_smlc->sccp_user = osmo_sccp_user_bind(sccp, "SMLC", sccp_sap_up, OSMO_SCCP_SSN_SMLC_BSSAP); - if (!g_smlc->sccp_user) - return -EINVAL; - - return 0; -} diff --git a/src/osmo-smlc/smlc_subscr.c b/src/osmo-smlc/smlc_subscr.c new file mode 100644 index 0000000..bfbb1e9 --- /dev/null +++ b/src/osmo-smlc/smlc_subscr.c @@ -0,0 +1,125 @@ +/* GSM subscriber details for use in SMLC */ +/* + * (C) 2020 by sysmocom s.f.m.c. GmbH <info at sysmocom.de> + * + * Author: Neels Hofmeyr <neels at hofmeyr.de> + * + * All Rights Reserved + * + * 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/smlc/debug.h> +#include <osmocom/smlc/smlc_data.h> +#include <osmocom/smlc/smlc_subscr.h> + +static void smlc_subscr_free(struct smlc_subscr *smlc_subscr) +{ + llist_del(&smlc_subscr->entry); + talloc_free(smlc_subscr); +} + +static int smlc_subscr_use_cb(struct osmo_use_count_entry *e, int32_t old_use_count, const char *file, int line) +{ + struct smlc_subscr *smlc_subscr = e->use_count->talloc_object; + int32_t total; + int level; + + if (!e->use) + return -EINVAL; + + total = osmo_use_count_total(&smlc_subscr->use_count); + + if (total == 0 + || (total == 1 && old_use_count == 0 && e->count == 1)) + level = LOGL_INFO; + else + level = LOGL_DEBUG; + + LOGPSRC(DREF, level, file, line, "%s: %s %s\n", + smlc_subscr_to_str_c(OTC_SELECT, smlc_subscr), + (e->count - old_use_count) > 0? "+" : "-", e->use); + + if (e->count < 0) + return -ERANGE; + + if (total == 0) + smlc_subscr_free(smlc_subscr); + return 0; +} + +static struct smlc_subscr *smlc_subscr_alloc() +{ + struct smlc_subscr *smlc_subscr; + + smlc_subscr = talloc_zero(g_smlc, struct smlc_subscr); + if (!smlc_subscr) + return NULL; + + smlc_subscr->use_count = (struct osmo_use_count){ + .talloc_object = smlc_subscr, + .use_cb = smlc_subscr_use_cb, + }; + + llist_add_tail(&smlc_subscr->entry, &g_smlc->subscribers); + + return smlc_subscr; +} + +struct smlc_subscr *smlc_subscr_find(const struct osmo_mobile_identity *imsi, const char *use_token) +{ + struct smlc_subscr *smlc_subscr; + if (!imsi) + return NULL; + + llist_for_each_entry(smlc_subscr, &g_smlc->subscribers, entry) { + if (!osmo_mobile_identity_cmp(&smlc_subscr->imsi, imsi)) { + smlc_subscr_get(smlc_subscr, use_token); + return smlc_subscr; + } + } + return NULL; +} + +struct smlc_subscr *smlc_subscr_find_or_create(const struct osmo_mobile_identity *imsi, const char *use_token) +{ + struct smlc_subscr *smlc_subscr; + if (!imsi) + return NULL; + smlc_subscr = smlc_subscr_find(imsi, use_token); + if (smlc_subscr) + return smlc_subscr; + smlc_subscr = smlc_subscr_alloc(); + if (!smlc_subscr) + return NULL; + smlc_subscr->imsi = *imsi; + smlc_subscr_get(smlc_subscr, use_token); + return smlc_subscr; +} + +int smlc_subscr_to_str_buf(char *buf, size_t buf_len, const struct smlc_subscr *smlc_subscr) +{ + struct osmo_strbuf sb = { .buf = buf, .len = buf_len }; + OSMO_STRBUF_APPEND(sb, osmo_mobile_identity_to_str_buf, &smlc_subscr->imsi); + OSMO_STRBUF_PRINTF(sb, "["); + OSMO_STRBUF_APPEND(sb, osmo_use_count_to_str_buf, &smlc_subscr->use_count); + OSMO_STRBUF_PRINTF(sb, "]"); + return sb.chars_needed; +} + +char *smlc_subscr_to_str_c(void *ctx, const struct smlc_subscr *smlc_subscr) +{ + OSMO_NAME_C_IMPL(ctx, 64, "ERROR", smlc_subscr_to_str_buf, smlc_subscr) +} diff --git a/tests/Makefile.am b/tests/Makefile.am index 0cbf998..9487d3a 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,4 +1,5 @@ SUBDIRS = \ + smlc_subscr \ $(NULL) # The `:;' works around a Bash 3.2 bug when the output is not writeable. @@ -25,6 +26,8 @@ $(TESTSUITE) \ test_nodes.vty \ test_nodes.ctrl \ + cell_locations.vty \ + osmo-smlc.cfg \ $(NULL) TESTSUITE = $(srcdir)/testsuite @@ -51,7 +54,7 @@ vty-test: osmo_verify_transcript_vty.py -v \ -n OsmoSMLC -p 4271 \ - -r "$(top_builddir)/src/osmo-smlc/osmo-smlc -c $(top_srcdir)/doc/examples/osmo-smlc/osmo-smlc.cfg" \ + -r "$(top_builddir)/src/osmo-smlc/osmo-smlc -c $(top_srcdir)/tests/osmo-smlc.cfg" \ $(U) $(srcdir)/$(VTY_TEST) # To update the CTRL script from current application behavior, @@ -61,7 +64,7 @@ -rm -f $(CTRL_TEST_DB) osmo_verify_transcript_ctrl.py -v \ -p 4272 \ - -r "$(top_builddir)/src/osmo-smlc/osmo-smlc -c $(top_srcdir)/doc/examples/osmo-smlc/osmo-smlc.cfg" \ + -r "$(top_builddir)/src/osmo-smlc/osmo-smlc -c $(top_srcdir)/tests/osmo-smlc.cfg" \ $(U) $(srcdir)/*.ctrl -rm -f $(CTRL_TEST_DB) -rm $(CTRL_TEST_DB)-* diff --git a/tests/cell_locations.vty b/tests/cell_locations.vty new file mode 100644 index 0000000..0affb1e --- /dev/null +++ b/tests/cell_locations.vty @@ -0,0 +1,133 @@ +OsmoSMLC> enable + +OsmoSMLC# show cells +% No cell locations are configured + +OsmoSMLC# configure terminal + + +OsmoSMLC(config)# cells? + cells Configure cell locations + +OsmoSMLC(config)# cells +OsmoSMLC(config-cells)# list +... + lac-ci <0-65535> <0-65535> lat LATITUDE lon LONGITUDE + no lac-ci <0-65535> <0-65535> + cgi <0-999> <0-999> <0-65535> <0-65535> lat LATITUDE lon LONGITUDE + no cgi <0-999> <0-999> <0-65535> <0-65535> + +OsmoSMLC(config-cells)# lac-ci? + lac-ci Cell location by LAC and CI +OsmoSMLC(config-cells)# lac-ci ? + <0-65535> LAC +OsmoSMLC(config-cells)# lac-ci 23 ? + <0-65535> CI +OsmoSMLC(config-cells)# lac-ci 23 42 ? + lat Global latitute coordinate +OsmoSMLC(config-cells)# lac-ci 23 42 lat ? + LATITUDE Latitude floating-point number, -90.0 (S) to 90.0 (N) +OsmoSMLC(config-cells)# lac-ci 23 42 lat 23.23 ? + lon Global longitude coordinate +OsmoSMLC(config-cells)# lac-ci 23 42 lat 23.23 lon ? + LONGITUDE Longitude as floating-point number, -180.0 (W) to 180.0 (E) +OsmoSMLC(config-cells)# lac-ci 23 42 lat 23.23 lon 42.42 ? + <cr> + +OsmoSMLC(config-cells)# cgi? + cgi Cell location by Cell-Global ID +OsmoSMLC(config-cells)# cgi ? + <0-999> MCC +OsmoSMLC(config-cells)# cgi 001 ? + <0-999> MNC +OsmoSMLC(config-cells)# cgi 001 02 ? + <0-65535> LAC +OsmoSMLC(config-cells)# cgi 001 02 3 ? + <0-65535> CI +OsmoSMLC(config-cells)# cgi 001 02 3 4 ? + lat Global latitute coordinate +OsmoSMLC(config-cells)# cgi 001 02 3 4 lat ? + LATITUDE Latitude floating-point number, -90.0 (S) to 90.0 (N) +OsmoSMLC(config-cells)# cgi 001 02 3 4 lat 1.1 ? + lon Global longitude coordinate +OsmoSMLC(config-cells)# cgi 001 02 3 4 lat 1.1 lon ? + LONGITUDE Longitude as floating-point number, -180.0 (W) to 180.0 (E) +OsmoSMLC(config-cells)# cgi 001 02 3 4 lat 1.1 lon 2.2 ? + <cr> + +OsmoSMLC(config-cells)# lac-ci 23 42 lat 23.23 lon 42.42 +OsmoSMLC(config-cells)# cgi 001 02 3 4 lat 1.1 lon 2.2 + +OsmoSMLC(config-cells)# do show cells +cells + lac-ci 23 42 lat 23.23 lon 42.42 + cgi 001 02 3 4 lat 1.1 lon 2.2 + +OsmoSMLC(config-cells)# show running-config + +Current configuration: +! +! +log stderr + logging filter all 1 + logging color 1 + logging print category-hex 1 + logging print category 0 + logging timestamp 0 + logging print file 1 + logging level smlc notice + logging level ref notice + logging level lb notice + logging level lcs notice + logging level lglobal notice + logging level llapd notice + logging level linp notice + logging level lmux notice + logging level lmi notice + logging level lmib notice + logging level lsms notice + logging level lctrl notice + logging level lgtp notice + logging level lstats notice + logging level lgsup notice + logging level loap notice + logging level lss7 notice + logging level lsccp notice + logging level lsua notice + logging level lm3ua notice + logging level lmgcp notice + logging level ljibuf notice + logging level lrspro notice + logging level lns notice +! +line vty + no login +! +cs7 instance 0 + point-code 0.23.6 +cells + lac-ci 23 42 lat 23.23 lon 42.42 + cgi 001 02 3 4 lat 1.1 lon 2.2 +end + +OsmoSMLC(config-cells)# no lac-ci 99 99 +% cannot remove, no such entry +OsmoSMLC(config-cells)# no cgi 009 08 7 6 +% cannot remove, no such entry + +OsmoSMLC(config-cells)# do show cells +cells + lac-ci 23 42 lat 23.23 lon 42.42 + cgi 001 02 3 4 lat 1.1 lon 2.2 + +OsmoSMLC(config-cells)# lac-ci 23 42 lat 17.17 lon 18.18 +OsmoSMLC(config-cells)# do show cells +cells + lac-ci 23 42 lat 17.17 lon 18.18 + cgi 001 02 3 4 lat 1.1 lon 2.2 + +OsmoSMLC(config-cells)# no lac-ci 23 42 +OsmoSMLC(config-cells)# no cgi 001 02 3 4 + +OsmoSMLC(config-cells)# do show cells +% No cell locations are configured diff --git a/tests/osmo-smlc.cfg b/tests/osmo-smlc.cfg new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/osmo-smlc.cfg diff --git a/tests/smlc_subscr/Makefile.am b/tests/smlc_subscr/Makefile.am new file mode 100644 index 0000000..9ed3b59 --- /dev/null +++ b/tests/smlc_subscr/Makefile.am @@ -0,0 +1,39 @@ +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/include \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + -ggdb3 \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOGSM_CFLAGS) \ + $(COVERAGE_CFLAGS) \ + $(NULL) + +AM_LDFLAGS = \ + $(COVERAGE_LDFLAGS) \ + $(NULL) + +EXTRA_DIST = \ + smlc_subscr_test.ok \ + smlc_subscr_test.err \ + $(NULL) + +noinst_PROGRAMS = \ + smlc_subscr_test \ + $(NULL) + +smlc_subscr_test_SOURCES = \ + smlc_subscr_test.c \ + $(NULL) + +smlc_subscr_test_LDADD = \ + $(top_builddir)/src/osmo-smlc/smlc_data.o \ + $(top_builddir)/src/osmo-smlc/smlc_subscr.o \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOGSM_LIBS) \ + $(NULL) + +update_exp: + $(builddir)/smlc_subscr_test >$(srcdir)/smlc_subscr_test.ok 2>$(srcdir)/smlc_subscr_test.err diff --git a/tests/smlc_subscr/smlc_subscr_test.c b/tests/smlc_subscr/smlc_subscr_test.c new file mode 100644 index 0000000..92b7293 --- /dev/null +++ b/tests/smlc_subscr/smlc_subscr_test.c @@ -0,0 +1,157 @@ +/* + * (C) 2020 by sysmocom s.f.m.c. GmbH <info at sysmocom.de> + * + * Author: Neels Hofmeyr <neels at hofmeyr.de> + * + * All Rights Reserved + * + * 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/smlc/debug.h> +#include <osmocom/smlc/smlc_data.h> +#include <osmocom/smlc/smlc_subscr.h> + +#include <osmocom/core/application.h> +#include <osmocom/core/utils.h> + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <inttypes.h> + +struct smlc_state *g_smlc; + +#define VERBOSE_ASSERT(val, expect_op, fmt) \ + do { \ + printf(#val " == " fmt "\n", (val)); \ + OSMO_ASSERT((val) expect_op); \ + } while (0); + +#define USE_FOO "foo" +#define USE_BAR "bar" + +static void assert_smlc_subscr(const struct smlc_subscr *smlc_subscr, const struct osmo_mobile_identity *imsi) +{ + struct smlc_subscr *sfound; + OSMO_ASSERT(smlc_subscr); + OSMO_ASSERT(osmo_mobile_identity_cmp(&smlc_subscr->imsi, imsi) == 0); + + sfound = smlc_subscr_find(imsi, __func__); + OSMO_ASSERT(sfound == smlc_subscr); + + smlc_subscr_put(sfound, __func__); +} + +static void test_smlc_subscr(void) +{ + struct smlc_subscr *s1, *s2, *s3; + const struct osmo_mobile_identity imsi1 = { .type = GSM_MI_TYPE_IMSI, .imsi = "1234567890", }; + const struct osmo_mobile_identity imsi2 = { .type = GSM_MI_TYPE_IMSI, .imsi = "9876543210", }; + const struct osmo_mobile_identity imsi3 = { .type = GSM_MI_TYPE_IMSI, .imsi = "423423", }; + + printf("Test SMLC subscriber allocation and deletion\n"); + + /* Check for emptiness */ + VERBOSE_ASSERT(llist_count(&g_smlc->subscribers), == 0, "%d"); + OSMO_ASSERT(smlc_subscr_find(&imsi1, "-") == NULL); + OSMO_ASSERT(smlc_subscr_find(&imsi2, "-") == NULL); + OSMO_ASSERT(smlc_subscr_find(&imsi3, "-") == NULL); + + /* Allocate entry 1 */ + s1 = smlc_subscr_find_or_create(&imsi1, USE_FOO); + VERBOSE_ASSERT(llist_count(&g_smlc->subscribers), == 1, "%d"); + assert_smlc_subscr(s1, &imsi1); + VERBOSE_ASSERT(llist_count(&g_smlc->subscribers), == 1, "%d"); + OSMO_ASSERT(smlc_subscr_find(&imsi2, "-") == NULL); + + /* Allocate entry 2 */ + s2 = smlc_subscr_find_or_create(&imsi2, USE_BAR); + VERBOSE_ASSERT(llist_count(&g_smlc->subscribers), == 2, "%d"); + + /* Allocate entry 3 */ + s3 = smlc_subscr_find_or_create(&imsi3, USE_FOO); + smlc_subscr_get(s3, USE_BAR); + VERBOSE_ASSERT(llist_count(&g_smlc->subscribers), == 3, "%d"); + + /* Check entries */ + assert_smlc_subscr(s1, &imsi1); + assert_smlc_subscr(s2, &imsi2); + assert_smlc_subscr(s3, &imsi3); + + /* Free entry 1 */ + smlc_subscr_put(s1, USE_FOO); + s1 = NULL; + VERBOSE_ASSERT(llist_count(&g_smlc->subscribers), == 2, "%d"); + OSMO_ASSERT(smlc_subscr_find(&imsi1, "-") == NULL); + + assert_smlc_subscr(s2, &imsi2); + assert_smlc_subscr(s3, &imsi3); + + /* Free entry 2 */ + smlc_subscr_put(s2, USE_BAR); + s2 = NULL; + VERBOSE_ASSERT(llist_count(&g_smlc->subscribers), == 1, "%d"); + OSMO_ASSERT(smlc_subscr_find(&imsi1, "-") == NULL); + OSMO_ASSERT(smlc_subscr_find(&imsi2, "-") == NULL); + assert_smlc_subscr(s3, &imsi3); + + /* Remove one use of entry 3 */ + smlc_subscr_put(s3, USE_BAR); + assert_smlc_subscr(s3, &imsi3); + VERBOSE_ASSERT(llist_count(&g_smlc->subscribers), == 1, "%d"); + + /* Free entry 3 */ + smlc_subscr_put(s3, USE_FOO); + s3 = NULL; + VERBOSE_ASSERT(llist_count(&g_smlc->subscribers), == 0, "%d"); + OSMO_ASSERT(smlc_subscr_find(&imsi3, "-") == NULL); + + OSMO_ASSERT(llist_empty(&g_smlc->subscribers)); +} + +static const struct log_info_cat log_categories[] = { + [DREF] = { + .name = "DREF", + .description = "Reference Counting", + .enabled = 1, .loglevel = LOGL_DEBUG, + }, +}; + +static const struct log_info log_info = { + .cat = log_categories, + .num_cat = ARRAY_SIZE(log_categories), +}; + +int main() +{ + void *ctx = talloc_named_const(NULL, 0, "smlc_subscr_test"); + + osmo_init_logging2(ctx, &log_info); + log_set_print_filename(osmo_stderr_target, 0); + log_set_print_timestamp(osmo_stderr_target, 0); + log_set_use_color(osmo_stderr_target, 0); + log_set_print_category(osmo_stderr_target, 1); + + g_smlc = smlc_state_alloc(ctx); + + printf("Testing SMLC subscriber code.\n"); + + test_smlc_subscr(); + + printf("Done\n"); + return 0; +} + diff --git a/tests/smlc_subscr/smlc_subscr_test.err b/tests/smlc_subscr/smlc_subscr_test.err new file mode 100644 index 0000000..8e0d1fa --- /dev/null +++ b/tests/smlc_subscr/smlc_subscr_test.err @@ -0,0 +1,24 @@ +DREF IMSI-1234567890[1 (foo)]: + foo +DREF IMSI-1234567890[2 (foo,assert_smlc_subscr)]: + assert_smlc_subscr +DREF IMSI-1234567890[1 (foo)]: - assert_smlc_subscr +DREF IMSI-9876543210[1 (bar)]: + bar +DREF IMSI-423423[1 (foo)]: + foo +DREF IMSI-423423[2 (foo,bar)]: + bar +DREF IMSI-1234567890[2 (foo,assert_smlc_subscr)]: + assert_smlc_subscr +DREF IMSI-1234567890[1 (foo)]: - assert_smlc_subscr +DREF IMSI-9876543210[2 (bar,assert_smlc_subscr)]: + assert_smlc_subscr +DREF IMSI-9876543210[1 (bar)]: - assert_smlc_subscr +DREF IMSI-423423[3 (foo,bar,assert_smlc_subscr)]: + assert_smlc_subscr +DREF IMSI-423423[2 (foo,bar)]: - assert_smlc_subscr +DREF IMSI-1234567890[0 (-)]: - foo +DREF IMSI-9876543210[2 (bar,assert_smlc_subscr)]: + assert_smlc_subscr +DREF IMSI-9876543210[1 (bar)]: - assert_smlc_subscr +DREF IMSI-423423[3 (foo,bar,assert_smlc_subscr)]: + assert_smlc_subscr +DREF IMSI-423423[2 (foo,bar)]: - assert_smlc_subscr +DREF IMSI-9876543210[0 (-)]: - bar +DREF IMSI-423423[3 (foo,bar,assert_smlc_subscr)]: + assert_smlc_subscr +DREF IMSI-423423[2 (foo,bar)]: - assert_smlc_subscr +DREF IMSI-423423[1 (foo)]: - bar +DREF IMSI-423423[2 (foo,assert_smlc_subscr)]: + assert_smlc_subscr +DREF IMSI-423423[1 (foo)]: - assert_smlc_subscr +DREF IMSI-423423[0 (-)]: - foo diff --git a/tests/smlc_subscr/smlc_subscr_test.ok b/tests/smlc_subscr/smlc_subscr_test.ok new file mode 100644 index 0000000..c85007d --- /dev/null +++ b/tests/smlc_subscr/smlc_subscr_test.ok @@ -0,0 +1,12 @@ +Testing SMLC subscriber code. +Test SMLC subscriber allocation and deletion +llist_count(&g_smlc->subscribers) == 0 +llist_count(&g_smlc->subscribers) == 1 +llist_count(&g_smlc->subscribers) == 1 +llist_count(&g_smlc->subscribers) == 2 +llist_count(&g_smlc->subscribers) == 3 +llist_count(&g_smlc->subscribers) == 2 +llist_count(&g_smlc->subscribers) == 1 +llist_count(&g_smlc->subscribers) == 1 +llist_count(&g_smlc->subscribers) == 0 +Done diff --git a/tests/testsuite.at b/tests/testsuite.at index 09a77c3..0a3b9bb 100644 --- a/tests/testsuite.at +++ b/tests/testsuite.at @@ -1,2 +1,9 @@ AT_INIT AT_BANNER([Regression tests.]) + +AT_SETUP([smlc_subscr]) +AT_KEYWORDS([smlc_subscr]) +cat $abs_srcdir/smlc_subscr/smlc_subscr_test.ok > expout +cat $abs_srcdir/smlc_subscr/smlc_subscr_test.err > experr +AT_CHECK([$abs_top_builddir/tests/smlc_subscr/smlc_subscr_test], [], [expout], [experr]) +AT_CLEANUP -- To view, visit https://gerrit.osmocom.org/c/osmo-smlc/+/20470 To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings Gerrit-Project: osmo-smlc Gerrit-Branch: master Gerrit-Change-Id: I917ba8fc51a1f1150be77ae01e12a7b16a853052 Gerrit-Change-Number: 20470 Gerrit-PatchSet: 1 Gerrit-Owner: neels <nhofmeyr at sysmocom.de> Gerrit-MessageType: newchange -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://lists.osmocom.org/pipermail/gerrit-log/attachments/20201007/fb53843e/attachment.htm>