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
This commit is contained in:
Neels Hofmeyr 2020-09-19 02:36:08 +02:00
parent 97362c57bc
commit 7299215d74
31 changed files with 2684 additions and 114 deletions

View File

@ -212,6 +212,7 @@ AC_OUTPUT(
src/osmo-smlc/Makefile
tests/Makefile
tests/atlocal
tests/smlc_subscr/Makefile
doc/Makefile
doc/examples/Makefile
doc/manuals/Makefile

View File

@ -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

View File

@ -1,4 +1,12 @@
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 \
smlc_vty.h \
$(NULL)

View File

@ -0,0 +1,49 @@
/* OsmoSMLC cell locations configuration */
/*
* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* Author: Neels Hofmeyr <neels@hofmeyr.de>
*
* SPDX-License-Identifier: GPL-2.0+
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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();

View File

@ -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,
};

View File

@ -0,0 +1,55 @@
#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);
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);

View File

@ -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);

View File

@ -0,0 +1,63 @@
/* 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;
};
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);

View File

@ -8,25 +8,26 @@
#include <osmocom/ctrl/control_if.h>
#include <osmocom/sigtran/sccp_sap.h>
struct osmo_sccp_instance;
struct sccp_lb_inst;
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 +36,25 @@ enum smlc_ctrl_node {
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,
};

View File

@ -0,0 +1,65 @@
/* Handle LCS BSSMAP-LE Perform Location Request */
/*
* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* Author: Neels Hofmeyr <neels@hofmeyr.de>
*
* SPDX-License-Identifier: GPL-2.0+
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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);

View File

@ -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);

View File

@ -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)

View File

@ -0,0 +1,7 @@
#pragma once
#include <osmocom/vty/command.h>
enum smlc_vty_node {
CELLS_NODE = _LAST_OSMOVTY_NODE + 1,
};

View File

@ -23,9 +23,15 @@ bin_PROGRAMS = \
$(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 = \

View File

@ -0,0 +1,318 @@
/* OsmoSMLC cell locations configuration */
/*
* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* Author: Neels Hofmeyr <neels@hofmeyr.de>
*
* SPDX-License-Identifier: GPL-2.0+
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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 omnidirectional 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;
}

198
src/osmo-smlc/lb_conn.c Normal file
View File

@ -0,0 +1,198 @@
/* SMLC Lb connection implementation */
/*
* (C) 2020 by sysmocom s.m.f.c. <info@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,
},
};
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 = lb_conn_down_l2_co(lb_conn, msg, false);
msgb_free(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);
}

495
src/osmo-smlc/lb_peer.c Normal file
View File

@ -0,0 +1,495 @@
/*
* (C) 2019 by sysmocom - s.m.f.c. GmbH <info@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;
int rc;
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;
}
rc = sccp_lb_down_l2_cl(lbp->sli, &lbp->peer_addr, msg);
msgb_free(msg);
if (rc) {
LOG_LB_PEER(lbp, LOGL_ERROR, "Failed to send RESET message\n");
lb_peer_state_chg(lbp, LB_PEER_ST_WAIT_RX_RESET);
}
}
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);
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;
}
}
}

View File

@ -0,0 +1,253 @@
/*
* (C) 2020 by sysmocom - s.m.f.c. GmbH <info@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_CL(sli, peer_addr, 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);
}

65
src/osmo-smlc/smlc_data.c Normal file
View File

@ -0,0 +1,65 @@
/* (C) 2020 by Harald Welte <laforge@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;
}

View File

@ -0,0 +1,445 @@
/* Handle LCS BSSMAP-LE Perform Location Request */
/*
* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* Author: Neels Hofmeyr <neels@hofmeyr.de>
*
* SPDX-License-Identifier: GPL-2.0+
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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", \
smlc_loc_req ? osmo_fsm_inst_state_name(smlc_loc_req->fi) : "NULL", ## args); \
smlc_loc_req->lcs_cause = (struct lcs_cause_ie){ \
.present = true, \
.cause_val = cause, \
}; \
smlc_loc_req_fsm_state_chg(smlc_loc_req->fi, SMLC_LOC_REQ_ST_FAILED); \
} while(0)
static struct smlc_loc_req *smlc_loc_req_alloc(void *ctx)
{
struct smlc_loc_req *smlc_loc_req;
struct osmo_fsm_inst *fi = osmo_fsm_inst_alloc(&smlc_loc_req_fsm, ctx, NULL, LOGL_DEBUG, "no-id");
OSMO_ASSERT(fi);
smlc_loc_req = talloc(fi, struct smlc_loc_req);
OSMO_ASSERT(smlc_loc_req);
fi->priv = smlc_loc_req;
*smlc_loc_req = (struct smlc_loc_req){
.fi = fi,
};
return smlc_loc_req;
}
static int smlc_loc_req_start(struct lb_conn *lb_conn, const struct bssmap_le_perform_loc_req *loc_req_pdu)
{
struct smlc_loc_req *smlc_loc_req;
rate_ctr_inc(&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;
struct osmo_gad location;
int rc;
if (!smlc_loc_req->ta_present) {
smlc_loc_req_fail(LCS_CAUSE_SYSTEM_FAILURE,
"Internal error: GOT_TA event, but no TA present");
return;
}
bssmap_le = (struct bssmap_le_pdu){
.msg_type = BSSMAP_LE_MSGT_PERFORM_LOC_RESP,
.perform_loc_resp = {
.location_estimate_present = true,
},
};
rc = cell_location_from_ta(&location, &smlc_loc_req->latest_cell_id, smlc_loc_req->ta);
if (rc) {
smlc_loc_req_fail(LCS_CAUSE_FACILITY_NOTSUPP, "Unable to compose Location Estimate for %s: %s",
gsm0808_cell_id_name_c(OTC_SELECT, &smlc_loc_req->latest_cell_id),
rc == -ENOENT ? "No location information for this cell" : "unknown error");
return;
}
rc = osmo_gad_enc(&bssmap_le.perform_loc_resp.location_estimate, &location);
if (rc <= 0) {
smlc_loc_req_fail(LCS_CAUSE_FACILITY_NOTSUPP, "Unable to encode Location Estimate for %s (rc=%d)",
gsm0808_cell_id_name_c(OTC_SELECT, &smlc_loc_req->latest_cell_id), rc);
return;
}
LOG_SMLC_LOC_REQ(smlc_loc_req, LOGL_INFO, "Returning location estimate to BSC: %s TA=%u --> %s\n",
gsm0808_cell_id_name_c(OTC_SELECT, &smlc_loc_req->latest_cell_id),
smlc_loc_req->ta, osmo_gad_to_str_c(OTC_SELECT, &location));
if (lb_conn_send_bssmap_le(smlc_loc_req->lb_conn, &bssmap_le)) {
smlc_loc_req_fail(LCS_CAUSE_SYSTEM_FAILURE,
"Unable to encode/send BSSMAP-LE Perform Location Response");
return;
}
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
}
static void smlc_loc_req_failed_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
struct smlc_loc_req *smlc_loc_req = fi->priv;
struct bssmap_le_pdu bssmap_le = {
.msg_type = BSSMAP_LE_MSGT_PERFORM_LOC_RESP,
.perform_loc_resp = {
.lcs_cause = smlc_loc_req->lcs_cause,
},
};
int rc;
rc = lb_conn_send_bssmap_le(smlc_loc_req->lb_conn, &bssmap_le);
osmo_fsm_inst_term(fi, rc ? OSMO_FSM_TERM_ERROR : OSMO_FSM_TERM_REGULAR, NULL);
}
void smlc_loc_req_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
{
struct smlc_loc_req *smlc_loc_req = fi->priv;
if (smlc_loc_req->lb_conn && smlc_loc_req->lb_conn->smlc_loc_req == smlc_loc_req) {
smlc_loc_req->lb_conn->smlc_loc_req = NULL;
lb_conn_put(smlc_loc_req->lb_conn, LB_CONN_USE_SMLC_LOC_REQ);
}
}
#define S(x) (1 << (x))
static const struct osmo_fsm_state smlc_loc_req_fsm_states[] = {
[SMLC_LOC_REQ_ST_INIT] = {
.name = "INIT",
.out_state_mask = 0
| S(SMLC_LOC_REQ_ST_WAIT_TA)
| S(SMLC_LOC_REQ_ST_FAILED)
,
},
[SMLC_LOC_REQ_ST_WAIT_TA] = {
.name = "WAIT_TA",
.in_event_mask = 0
| S(SMLC_LOC_REQ_EV_RX_TA_RESPONSE)
| S(SMLC_LOC_REQ_EV_RX_BSSLAP_RESET)
| S(SMLC_LOC_REQ_EV_RX_LE_PERFORM_LOCATION_ABORT)
,
.out_state_mask = 0
| S(SMLC_LOC_REQ_ST_GOT_TA)
| S(SMLC_LOC_REQ_ST_FAILED)
,
.onenter = smlc_loc_req_wait_ta_onenter,
.action = smlc_loc_req_wait_ta_action,
},
[SMLC_LOC_REQ_ST_GOT_TA] = {
.name = "GOT_TA",
.out_state_mask = 0
| S(SMLC_LOC_REQ_ST_FAILED)
,
.onenter = smlc_loc_req_got_ta_onenter,
},
[SMLC_LOC_REQ_ST_FAILED] = {
.name = "FAILED",
.onenter = smlc_loc_req_failed_onenter,
},
};
static struct osmo_fsm smlc_loc_req_fsm = {
.name = "smlc_loc_req",
.states = smlc_loc_req_fsm_states,
.num_states = ARRAY_SIZE(smlc_loc_req_fsm_states),
.log_subsys = DLCS,
.event_names = smlc_loc_req_fsm_event_names,
.timer_cb = smlc_loc_req_fsm_timer_cb,
.cleanup = smlc_loc_req_fsm_cleanup,
};
static __attribute__((constructor)) void smlc_loc_req_fsm_register(void)
{
OSMO_ASSERT(osmo_fsm_register(&smlc_loc_req_fsm) == 0);
}

View File

@ -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 void signal_handler(int signal)
}
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 @@ const struct log_info log_info = {
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 @@ int main(int argc, char **argv)
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 @@ int main(int argc, char **argv)
}
*/
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);

View File

@ -1,94 +0,0 @@
/* (C) 2020 by Harald Welte <laforge@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;
}

125
src/osmo-smlc/smlc_subscr.c Normal file
View File

@ -0,0 +1,125 @@
/* GSM subscriber details for use in SMLC */
/*
* (C) 2020 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
*
* Author: Neels Hofmeyr <neels@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)
}

View File

@ -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 @@ EXTRA_DIST = \
$(TESTSUITE) \
test_nodes.vty \
test_nodes.ctrl \
cell_locations.vty \
osmo-smlc.cfg \
$(NULL)
TESTSUITE = $(srcdir)/testsuite
@ -51,7 +54,7 @@ VTY_TEST ?= *.vty
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 @@ ctrl-test:
-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)-*

92
tests/cell_locations.vty Normal file
View File

@ -0,0 +1,92 @@
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
...
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)# 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

0
tests/osmo-smlc.cfg Normal file
View File

View File

@ -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

View File

@ -0,0 +1,157 @@
/*
* (C) 2020 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
*
* Author: Neels Hofmeyr <neels@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;
}

View File

@ -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

View File

@ -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

View File

@ -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