osmo-bsc/src/osmo-bsc/neighbor_ident_ctrl.c

754 lines
18 KiB
C

/* CTRL interface implementation to manage identity of neighboring BSS cells for inter-BSC handover. */
/* (C) 2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
*
* All Rights Reserved
*
* Author: Philipp Maier <pmaier@sysmocom.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <errno.h>
#include <inttypes.h>
#include <time.h>
#include <osmocom/ctrl/control_cmd.h>
#include <osmocom/bsc/neighbor_ident.h>
#include <osmocom/bsc/gsm_data.h>
#include <osmocom/bsc/bsc_msc_data.h>
#include <osmocom/bsc/bts.h>
#include <osmocom/bsc/vty.h>
/* Continue to parse ARFCN and BSIC, which are optional parameters at the end of the parameter string in most of the
* commands. The result is ignored when parameter n is set to NULL. */
static int continue_parse_arfcn_and_bsic(char **saveptr, struct neighbor *n)
{
int arfcn;
int bsic;
char *tok;
tok = strtok_r(NULL, "-", saveptr);
/* No ARFCN and BSIC persent - stop */
if (!tok)
return 0;
if (osmo_str_to_int(&arfcn, tok, 10, 0, 1023) < 0)
return -EINVAL;
tok = strtok_r(NULL, "-", saveptr);
/* When an ARFCN is given, then the BSIC parameter is
* mandatory */
if (!tok)
return -EINVAL;
if (strcmp(tok, "any") == 0) {
bsic = BSIC_ANY;
} else {
if (osmo_str_to_int(&bsic, tok, 10, 0, 63) < 0)
return 1;
}
/* Make sure there are no excess parameters */
if (strtok_r(NULL, "-", saveptr))
return -EINVAL;
if (n) {
n->cell_id.ab_present = true;
n->cell_id.ab.arfcn = arfcn;
n->cell_id.ab.bsic = bsic;
}
return 0;
}
/* This and the following: Add/Remove a BTS as neighbor */
static int verify_neighbor_bts(struct ctrl_cmd *cmd, const char *value, void *_data)
{
struct gsm_bts *bts = cmd->node;
const int neigh_bts_nr = atoi(value);
struct gsm_bts *neigh_bts = gsm_bts_num(bts->network, neigh_bts_nr);
if (!neigh_bts) {
cmd->reply = "Invalid Neighbor BTS number - no such BTS";
return 1;
}
return 0;
}
static int verify_neighbor_bts_add(struct ctrl_cmd *cmd, const char *value, void *_data)
{
return verify_neighbor_bts(cmd, value, _data);
}
static int get_neighbor_bts_list(struct ctrl_cmd *cmd, void *data)
{
/* Max. 256 BTS neighbors (as of now, any bts can be its own neighbor per cfg) comma-separated ->
* max. 255 commas * + trailing '\0': 256
* 10 of those numbers (0...9) are 1-digit numbers: + 10 = 266
* 90 of those numbers are 2-digit numbers (10...99): + 90 = 356
* 255 - 100 + 1 = 156 are 3-digit numbers (100...255): + 156 = 512 bytes
* Double BTS num entries are not possible (check exists and is being tested against in python tests). */
char log_buf[512];
struct osmo_strbuf reply = { .buf = log_buf,
.len = sizeof(log_buf),
.pos = log_buf
};
struct gsm_bts *neighbor_bts, *bts = (struct gsm_bts *)cmd->node;
if (!bts) {
cmd->reply = "BTS not found";
return CTRL_CMD_ERROR;
}
struct neighbor *n;
llist_for_each_entry(n, &bts->neighbors, entry)
if (resolve_local_neighbor(&neighbor_bts, bts, n) == 0)
OSMO_STRBUF_PRINTF(reply, "%" PRIu8 ",", neighbor_bts->nr);
if (reply.buf == reply.pos)
cmd->reply = "";
else { /* Get rid of trailing comma */
reply.pos[-1] = '\0';
if (!(cmd->reply = talloc_strdup(cmd, reply.buf)))
goto oom;
}
return CTRL_CMD_REPLY;
oom:
cmd->reply = "OOM";
return CTRL_CMD_ERROR;
}
CTRL_CMD_DEFINE_RO(neighbor_bts_list, "neighbor-bts list");
static int set_neighbor_bts_add(struct ctrl_cmd *cmd, void *data)
{
struct gsm_bts *bts = cmd->node;
const int bts_nr = atoi(cmd->value);
int rc;
struct neighbor n = {
.type = NEIGHBOR_TYPE_BTS_NR,
.bts_nr = bts_nr,
};
rc = neighbor_ident_add_neighbor(NULL, bts, &n);
if (rc != CMD_SUCCESS) {
cmd->reply = "Failed to add neighbor";
return CTRL_CMD_ERROR;
}
cmd->reply = "OK";
return CTRL_CMD_REPLY;
}
/* Parameter format: "<num>"
* num: BTS number (0-255) */
CTRL_CMD_DEFINE_WO(neighbor_bts_add, "neighbor-bts add");
static int verify_neighbor_bts_del(struct ctrl_cmd *cmd, const char *value, void *_data)
{
return verify_neighbor_bts(cmd, value, _data);
}
static int set_neighbor_bts_del(struct ctrl_cmd *cmd, void *data)
{
struct gsm_bts *bts = cmd->node;
const int bts_nr = atoi(cmd->value);
int rc;
struct neighbor n = {
.type = NEIGHBOR_TYPE_BTS_NR,
.bts_nr = bts_nr,
};
rc = neighbor_ident_del_neighbor(NULL, bts, &n);
if (rc != CMD_SUCCESS) {
cmd->reply = "Failed to delete neighbor";
return CTRL_CMD_ERROR;
}
cmd->reply = "OK";
return CTRL_CMD_REPLY;
}
/* Parameter format: (see "add" command above) */
CTRL_CMD_DEFINE_WO(neighbor_bts_del, "neighbor-bts del");
/* This and the following: Add/Remove a LAC as neighbor */
static int parse_lac(void *ctx, struct neighbor *n, const char *value)
{
char *tmp = NULL, *tok, *saveptr;
int rc = 0;
int lac;
if (n)
memset(n, 0, sizeof(*n));
tmp = talloc_strdup(ctx, value);
if (!tmp)
return -EINVAL;
/* Parse LAC */
tok = strtok_r(tmp, "-", &saveptr);
if (tok) {
if (osmo_str_to_int(&lac, tok, 10, 0, 65535) < 0) {
rc = -EINVAL;
goto exit;
}
} else {
rc = -EINVAL;
goto exit;
}
/* Optional parameters: ARFCN and BSIC */
if (continue_parse_arfcn_and_bsic(&saveptr, n)) {
rc = -EINVAL;
goto exit;
}
if (n) {
n->type = NEIGHBOR_TYPE_CELL_ID;
n->cell_id.id.id_discr = CELL_IDENT_LAC;
n->cell_id.id.id.lac = lac;
}
exit:
talloc_free(tmp);
return rc;
}
static int verify_neighbor_lac_add(struct ctrl_cmd *cmd, const char *value, void *_data)
{
if (parse_lac(cmd, NULL, value))
return 1;
return 0;
}
static int set_neighbor_lac_add(struct ctrl_cmd *cmd, void *data)
{
struct gsm_bts *bts = cmd->node;
int rc;
struct neighbor n;
parse_lac(cmd, &n, cmd->value);
rc = neighbor_ident_add_neighbor(NULL, bts, &n);
if (rc != CMD_SUCCESS) {
cmd->reply = "Failed to add neighbor";
return CTRL_CMD_ERROR;
}
cmd->reply = "OK";
return CTRL_CMD_REPLY;
}
/* Parameter format: "<lac>[-<arfcn>-<bsic>]"
* lac: Location area of neighbor cell (0-65535)
* arfcn: ARFCN of neighbor cell (0-1023)
* bsic: BSIC of neighbor cell */
CTRL_CMD_DEFINE_WO(neighbor_lac_add, "neighbor-lac add");
static int verify_neighbor_lac_del(struct ctrl_cmd *cmd, const char *value, void *_data)
{
if (parse_lac(cmd, NULL, value))
return 1;
return 0;
}
static int set_neighbor_lac_del(struct ctrl_cmd *cmd, void *data)
{
struct gsm_bts *bts = cmd->node;
int rc;
struct neighbor n;
parse_lac(cmd, &n, cmd->value);
rc = neighbor_ident_del_neighbor(NULL, bts, &n);
if (rc != CMD_SUCCESS) {
cmd->reply = "Failed to delete neighbor";
return CTRL_CMD_ERROR;
}
cmd->reply = "OK";
return CTRL_CMD_REPLY;
}
/* Parameter format: (see "add" command above) */
CTRL_CMD_DEFINE_WO(neighbor_lac_del, "neighbor-lac del");
/* This and the following: Add/Remove a LAC-CI as neighbor */
static int parse_lac_ci(void *ctx, struct neighbor *n, const char *value)
{
char *tmp = NULL, *tok, *saveptr;
int rc = 0;
int lac;
int ci;
if (n)
memset(n, 0, sizeof(*n));
tmp = talloc_strdup(ctx, value);
if (!tmp)
return -EINVAL;
/* Parse LAC */
tok = strtok_r(tmp, "-", &saveptr);
if (tok) {
if (osmo_str_to_int(&lac, tok, 10, 0, 65535) < 0) {
rc = -EINVAL;
goto exit;
}
} else {
rc = -EINVAL;
goto exit;
}
/* Parse CI */
tok = strtok_r(NULL, "-", &saveptr);
if (tok) {
if (osmo_str_to_int(&ci, tok, 10, 0, 65535) < 0) {
rc = -EINVAL;
goto exit;
}
} else {
rc = -EINVAL;
goto exit;
}
/* Optional parameters: ARFCN and BSIC */
if (continue_parse_arfcn_and_bsic(&saveptr, n)) {
rc = -EINVAL;
goto exit;
}
if (n) {
n->type = NEIGHBOR_TYPE_CELL_ID;
n->cell_id.id.id_discr = CELL_IDENT_LAC_AND_CI;
n->cell_id.id.id.lac = lac;
n->cell_id.id.id.ci = ci;
}
exit:
talloc_free(tmp);
return rc;
}
static int verify_neighbor_lac_ci_add(struct ctrl_cmd *cmd, const char *value, void *_data)
{
if (parse_lac_ci(cmd, NULL, value))
return 1;
return 0;
}
static int set_neighbor_lac_ci_add(struct ctrl_cmd *cmd, void *data)
{
struct gsm_bts *bts = cmd->node;
int rc;
struct neighbor n;
parse_lac_ci(cmd, &n, cmd->value);
rc = neighbor_ident_add_neighbor(NULL, bts, &n);
if (rc != CMD_SUCCESS) {
cmd->reply = "Failed to add neighbor";
return CTRL_CMD_ERROR;
}
cmd->reply = "OK";
return CTRL_CMD_REPLY;
}
/* Parameter format: "<lac>-<ci>[-<arfcn>-<bsic>]"
* lac: Location area of neighbor cell (0-65535)
* ci: Cell ID of neighbor cell (0-65535)
* arfcn: ARFCN of neighbor cell (0-1023)
* bsic: BSIC of neighbor cell */
CTRL_CMD_DEFINE_WO(neighbor_lac_ci_add, "neighbor-lac-ci add");
static int verify_neighbor_lac_ci_del(struct ctrl_cmd *cmd, const char *value, void *_data)
{
if (parse_lac_ci(cmd, NULL, value))
return 1;
return 0;
}
static int set_neighbor_lac_ci_del(struct ctrl_cmd *cmd, void *data)
{
struct gsm_bts *bts = cmd->node;
int rc;
struct neighbor n;
parse_lac_ci(cmd, &n, cmd->value);
rc = neighbor_ident_del_neighbor(NULL, bts, &n);
if (rc != CMD_SUCCESS) {
cmd->reply = "Failed to delete neighbor";
return CTRL_CMD_ERROR;
}
cmd->reply = "OK";
return CTRL_CMD_REPLY;
}
/* Parameter format: (see "add" command above) */
CTRL_CMD_DEFINE_WO(neighbor_lac_ci_del, "neighbor-lac-ci del");
/* This and the following: Add/Remove a CGI as neighbor */
static int parse_cgi(void *ctx, struct neighbor *n, const char *value)
{
char *tmp = NULL, *tok, *saveptr;
int rc = 0;
uint16_t mcc;
uint16_t mnc;
bool mnc_3_digits;
int lac;
int ci;
if (n)
memset(n, 0, sizeof(*n));
tmp = talloc_strdup(ctx, value);
if (!tmp)
return -EINVAL;
/* Parse MCC */
tok = strtok_r(tmp, "-", &saveptr);
if (tok) {
if (osmo_mcc_from_str(tok, &mcc)) {
rc = -EINVAL;
goto exit;
}
} else {
rc = -EINVAL;
goto exit;
}
/* Parse MNC */
tok = strtok_r(NULL, "-", &saveptr);
if (tok) {
if (osmo_mnc_from_str(tok, &mnc, &mnc_3_digits)) {
rc = -EINVAL;
goto exit;
}
} else {
rc = -EINVAL;
goto exit;
}
/* Parse LAC */
tok = strtok_r(NULL, "-", &saveptr);
if (tok) {
if (osmo_str_to_int(&lac, tok, 10, 0, 65535) < 0) {
rc = -EINVAL;
goto exit;
}
} else {
rc = -EINVAL;
goto exit;
}
/* Parse CI */
tok = strtok_r(NULL, "-", &saveptr);
if (tok) {
if (osmo_str_to_int(&ci, tok, 10, 0, 65535) < 0) {
rc = -EINVAL;
goto exit;
}
} else {
rc = -EINVAL;
goto exit;
}
/* Optional parameters: ARFCN and BSIC */
if (continue_parse_arfcn_and_bsic(&saveptr, n)) {
rc = -EINVAL;
goto exit;
}
if (n) {
n->type = NEIGHBOR_TYPE_CELL_ID;
n->cell_id.id.id_discr = CELL_IDENT_WHOLE_GLOBAL;
n->cell_id.id.id.global.lai.lac = lac;
n->cell_id.id.id.global.lai.plmn.mcc = mcc;
n->cell_id.id.id.global.lai.plmn.mnc = mnc;
n->cell_id.id.id.global.lai.plmn.mnc_3_digits = mnc_3_digits;
n->cell_id.id.id.global.cell_identity = ci;
}
exit:
talloc_free(tmp);
return rc;
}
static int verify_neighbor_cgi_add(struct ctrl_cmd *cmd, const char *value, void *_data)
{
if (parse_cgi(cmd, NULL, value))
return 1;
return 0;
}
static int set_neighbor_cgi_add(struct ctrl_cmd *cmd, void *data)
{
struct gsm_bts *bts = cmd->node;
int rc;
struct neighbor n;
parse_cgi(cmd, &n, cmd->value);
rc = neighbor_ident_add_neighbor(NULL, bts, &n);
if (rc != CMD_SUCCESS) {
cmd->reply = "Failed to add neighbor";
return CTRL_CMD_ERROR;
}
cmd->reply = "OK";
return CTRL_CMD_REPLY;
}
/* Parameter format: "<mcc>-<mnc>-<lac>-<ci>[-<arfcn>-<bsic>]"
* mcc: Mobile country code of neighbor cell (0-999)
* mnc: Mobile network code of neighbor cell (0-999)
* lac: Location area of neighbor cell (0-65535)
* ci: Cell ID of neighbor cell (0-65535)
* arfcn: ARFCN of neighbor cell (0-1023)
* bsic: BSIC of neighbor cell */
CTRL_CMD_DEFINE_WO(neighbor_cgi_add, "neighbor-cgi add");
static int verify_neighbor_cgi_del(struct ctrl_cmd *cmd, const char *value, void *_data)
{
if (parse_cgi(cmd, NULL, value))
return 1;
return 0;
}
static int set_neighbor_cgi_del(struct ctrl_cmd *cmd, void *data)
{
struct gsm_bts *bts = cmd->node;
int rc;
struct neighbor n;
parse_cgi(cmd, &n, cmd->value);
rc = neighbor_ident_del_neighbor(NULL, bts, &n);
if (rc != CMD_SUCCESS) {
cmd->reply = "Failed to delete neighbor";
return CTRL_CMD_ERROR;
}
cmd->reply = "OK";
return CTRL_CMD_REPLY;
}
/* Parameter format: (see "add" command above) */
CTRL_CMD_DEFINE_WO(neighbor_cgi_del, "neighbor-cgi del");
/* This and the following: Add/Remove a CGI-PS as neighbor */
static int parse_cgi_ps(void *ctx, struct neighbor *n, const char *value)
{
char *tmp = NULL, *tok, *saveptr;
int rc = 0;
uint16_t mcc;
uint16_t mnc;
bool mnc_3_digits;
int lac;
int rac;
int ci;
if (n)
memset(n, 0, sizeof(*n));
tmp = talloc_strdup(ctx, value);
if (!tmp)
return -EINVAL;
/* Parse MCC */
tok = strtok_r(tmp, "-", &saveptr);
if (tok) {
if (osmo_mcc_from_str(tok, &mcc)) {
rc = -EINVAL;
goto exit;
}
} else {
rc = -EINVAL;
goto exit;
}
/* Parse MNC */
tok = strtok_r(NULL, "-", &saveptr);
if (tok) {
if (osmo_mnc_from_str(tok, &mnc, &mnc_3_digits)) {
rc = -EINVAL;
goto exit;
}
} else {
rc = -EINVAL;
goto exit;
}
/* Parse LAC */
tok = strtok_r(NULL, "-", &saveptr);
if (tok) {
if (osmo_str_to_int(&lac, tok, 10, 0, 65535) < 0) {
rc = -EINVAL;
goto exit;
}
} else {
rc = -EINVAL;
goto exit;
}
/* Parse RAC */
tok = strtok_r(NULL, "-", &saveptr);
if (tok) {
if (osmo_str_to_int(&rac, tok, 10, 0, 255) < 0) {
rc = -EINVAL;
goto exit;
}
} else {
rc = -EINVAL;
goto exit;
}
/* Parse CI */
tok = strtok_r(NULL, "-", &saveptr);
if (tok) {
if (osmo_str_to_int(&ci, tok, 10, 0, 65535) < 0) {
rc = -EINVAL;
goto exit;
}
} else {
rc = -EINVAL;
goto exit;
}
/* Optional parameters: ARFCN and BSIC */
if (continue_parse_arfcn_and_bsic(&saveptr, n)) {
rc = -EINVAL;
goto exit;
}
if (n) {
n->type = NEIGHBOR_TYPE_CELL_ID;
n->cell_id.id.id_discr = CELL_IDENT_WHOLE_GLOBAL_PS;
n->cell_id.id.id.global_ps.rai.lac.lac = lac;
n->cell_id.id.id.global_ps.rai.rac = lac;
n->cell_id.id.id.global_ps.rai.lac.plmn.mcc = mcc;
n->cell_id.id.id.global_ps.rai.lac.plmn.mnc = mnc;
n->cell_id.id.id.global_ps.rai.lac.plmn.mnc_3_digits = mnc_3_digits;
n->cell_id.id.id.global_ps.cell_identity = ci;
}
exit:
talloc_free(tmp);
return rc;
}
static int verify_neighbor_cgi_ps_add(struct ctrl_cmd *cmd, const char *value, void *_data)
{
if (parse_cgi_ps(cmd, NULL, value))
return 1;
return 0;
}
static int set_neighbor_cgi_ps_add(struct ctrl_cmd *cmd, void *data)
{
struct gsm_bts *bts = cmd->node;
int rc;
struct neighbor n;
parse_cgi_ps(cmd, &n, cmd->value);
rc = neighbor_ident_add_neighbor(NULL, bts, &n);
if (rc != CMD_SUCCESS) {
cmd->reply = "Failed to add neighbor";
return CTRL_CMD_ERROR;
}
cmd->reply = "OK";
return CTRL_CMD_REPLY;
}
/* Parameter format: "<mcc>-<mnc>-<lac>-<rac>-<ci>[-<arfcn>-<bsic>]"
* mcc: Mobile country code of neighbor cell (0-999)
* mnc: Mobile network code of neighbor cell (0-999)
* lac: Location area of neighbor cell (0-65535)
* rac: Routing area of neighbor cell (0-65535)
* ci: Cell ID of neighbor cell (0-65535)
* arfcn: ARFCN of neighbor cell (0-1023)
* bsic: BSIC of neighbor cell */
CTRL_CMD_DEFINE_WO(neighbor_cgi_ps_add, "neighbor-cgi-ps add");
static int verify_neighbor_cgi_ps_del(struct ctrl_cmd *cmd, const char *value, void *_data)
{
if (parse_cgi_ps(cmd, NULL, value))
return 1;
return 0;
}
static int set_neighbor_cgi_ps_del(struct ctrl_cmd *cmd, void *data)
{
struct gsm_bts *bts = cmd->node;
int rc;
struct neighbor n;
parse_cgi_ps(cmd, &n, cmd->value);
rc = neighbor_ident_del_neighbor(NULL, bts, &n);
if (rc != CMD_SUCCESS) {
cmd->reply = "Failed to delete neighbor";
return CTRL_CMD_ERROR;
}
cmd->reply = "OK";
return CTRL_CMD_REPLY;
}
/* Parameter format: (see "add" command above) */
CTRL_CMD_DEFINE_WO(neighbor_cgi_ps_del, "neighbor-cgi-ps del");
/* This and the following: clear all neighbor cell information */
static int set_neighbor_clear(struct ctrl_cmd *cmd, void *data)
{
struct gsm_bts *bts = cmd->node;
struct neighbor *neighbor;
struct neighbor *neighbor_tmp;
llist_for_each_entry_safe(neighbor, neighbor_tmp, &bts->neighbors, entry) {
llist_del(&neighbor->entry);
talloc_free(neighbor);
}
cmd->reply = "OK";
return CTRL_CMD_REPLY;
}
CTRL_CMD_DEFINE_WO_NOVRF(neighbor_clear, "neighbor-clear");
/* Register control interface commands implemented above */
int neighbor_ident_ctrl_init(void)
{
int rc = 0;
rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_neighbor_bts_add);
rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_neighbor_bts_del);
rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_neighbor_lac_add);
rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_neighbor_lac_del);
rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_neighbor_lac_ci_add);
rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_neighbor_lac_ci_del);
rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_neighbor_cgi_add);
rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_neighbor_cgi_del);
rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_neighbor_bts_list);
rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_neighbor_cgi_ps_add);
rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_neighbor_cgi_ps_del);
rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_neighbor_clear);
return rc;
}