496 lines
14 KiB
C
496 lines
14 KiB
C
/* Manage identity of neighboring BSS cells for inter-BSC handover.
|
|
*
|
|
* Measurement reports tell us about neighbor ARFCN and BSIC. If that ARFCN and BSIC is not managed by
|
|
* this local BSS, we need to tell the MSC a cell identity, like CGI, LAC+CI, etc. -- hence we need a
|
|
* mapping from ARFCN+BSIC to Cell Identifier List, which needs to be configured by the user.
|
|
*/
|
|
/* (C) 2018 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
|
|
*
|
|
* All Rights Reserved
|
|
*
|
|
* Author: Neels Hofmeyr <nhofmeyr@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 <osmocom/core/linuxlist.h>
|
|
#include <osmocom/core/utils.h>
|
|
#include <osmocom/gsm/gsm0808.h>
|
|
|
|
#include <osmocom/bsc/neighbor_ident.h>
|
|
|
|
#include <osmocom/ctrl/control_cmd.h>
|
|
#include <osmocom/ctrl/control_if.h>
|
|
|
|
#include <osmocom/bsc/gsm_data.h>
|
|
#include <osmocom/bsc/bts.h>
|
|
#include <osmocom/bsc/debug.h>
|
|
|
|
void bts_cell_ab(struct cell_ab *arfcn_bsic, const struct gsm_bts *bts)
|
|
{
|
|
*arfcn_bsic = (struct cell_ab){
|
|
.arfcn = bts->c0->arfcn,
|
|
.bsic = bts->bsic,
|
|
};
|
|
}
|
|
|
|
/* Find the local gsm_bts pointer that a specific other BTS' neighbor config refers to. Return NULL if there is no such
|
|
* local cell in this BSS.
|
|
*/
|
|
int resolve_local_neighbor(struct gsm_bts **local_neighbor_p, const struct gsm_bts *from_bts,
|
|
const struct neighbor *neighbor)
|
|
{
|
|
struct gsm_bts *bts;
|
|
struct gsm_bts *bts_exact = NULL;
|
|
struct gsm_bts *bts_wildcard = NULL;
|
|
*local_neighbor_p = NULL;
|
|
|
|
switch (neighbor->type) {
|
|
case NEIGHBOR_TYPE_BTS_NR:
|
|
bts = gsm_bts_num(bsc_gsmnet, neighbor->bts_nr);
|
|
goto check_bts;
|
|
|
|
case NEIGHBOR_TYPE_CELL_ID:
|
|
/* Find cell id below */
|
|
break;
|
|
|
|
default:
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
/* NEIGHBOR_TYPE_CELL_ID */
|
|
llist_for_each_entry(bts, &bsc_gsmnet->bts_list, list) {
|
|
struct gsm0808_cell_id cell_id;
|
|
gsm_bts_cell_id(&cell_id, bts);
|
|
|
|
if (gsm0808_cell_ids_match(&cell_id, &neighbor->cell_id.id, true)) {
|
|
if (bts_exact) {
|
|
LOGP(DHO, LOGL_ERROR,
|
|
"Neighbor config error: Multiple BTS match %s (BTS %u and BTS %u)\n",
|
|
gsm0808_cell_id_name_c(OTC_SELECT, &neighbor->cell_id.id),
|
|
bts_exact->nr, bts->nr);
|
|
return -EINVAL;
|
|
} else {
|
|
bts_exact = bts;
|
|
}
|
|
}
|
|
|
|
if (!bts_wildcard && gsm0808_cell_ids_match(&cell_id, &neighbor->cell_id.id, false))
|
|
bts_wildcard = bts;
|
|
}
|
|
|
|
bts = (bts_exact ? : bts_wildcard);
|
|
|
|
check_bts:
|
|
/* A cell cannot be its own neighbor */
|
|
if (bts == from_bts) {
|
|
LOGP(DHO, LOGL_ERROR,
|
|
"Neighbor config error: BTS %u -> %s: this cell is configured as its own neighbor\n",
|
|
from_bts->nr, neighbor_to_str_c(OTC_SELECT, neighbor));
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!bts)
|
|
return -ENOENT;
|
|
|
|
/* Double check whether ARFCN + BSIC config matches, if present. */
|
|
if (neighbor->cell_id.ab_present) {
|
|
struct cell_ab cell_ab;
|
|
bts_cell_ab(&cell_ab, bts);
|
|
if (!cell_ab_match(&cell_ab, &neighbor->cell_id.ab, false)) {
|
|
LOGP(DHO, LOGL_ERROR, "Neighbor config error: Local BTS %d matches %s, but not ARFCN+BSIC %s\n",
|
|
bts->nr, gsm0808_cell_id_name_c(OTC_SELECT, &neighbor->cell_id.id),
|
|
cell_ab_to_str_c(OTC_SELECT, &cell_ab));
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
*local_neighbor_p = bts;
|
|
return 0;
|
|
}
|
|
|
|
int resolve_neighbors(struct gsm_bts **local_neighbor_p, struct gsm0808_cell_id_list2 *remote_neighbors,
|
|
struct gsm_bts *from_bts, const struct cell_ab *target_ab, bool log_errors)
|
|
{
|
|
struct neighbor *n;
|
|
struct gsm_bts *local_neighbor = NULL;
|
|
struct gsm0808_cell_id_list2 remotes = {};
|
|
|
|
if (local_neighbor_p)
|
|
*local_neighbor_p = NULL;
|
|
if (remote_neighbors)
|
|
*remote_neighbors = (struct gsm0808_cell_id_list2){ 0 };
|
|
|
|
llist_for_each_entry(n, &from_bts->neighbors, entry) {
|
|
struct gsm_bts *neigh_bts;
|
|
if (resolve_local_neighbor(&neigh_bts, from_bts, n) == 0) {
|
|
/* This neighbor entry is a local cell neighbor. Do ARFCN and BSIC match? */
|
|
struct cell_ab ab;
|
|
bts_cell_ab(&ab, neigh_bts);
|
|
if (!cell_ab_match(&ab, target_ab, false))
|
|
continue;
|
|
|
|
/* Found a local cell neighbor that matches the target_ab */
|
|
|
|
/* If we already found one, these are ambiguous local neighbors */
|
|
if (local_neighbor) {
|
|
if (log_errors)
|
|
LOGP(DHO, LOGL_ERROR, "Neighbor config error:"
|
|
" Local BTS %d -> %s resolves to local neighbor BTSes %u *and* %u\n",
|
|
from_bts->nr, cell_ab_to_str_c(OTC_SELECT, target_ab), local_neighbor->nr,
|
|
neigh_bts->nr);
|
|
return -ENOTSUP;
|
|
}
|
|
local_neighbor = neigh_bts;
|
|
|
|
} else if (n->type == NEIGHBOR_TYPE_CELL_ID && n->cell_id.ab_present) {
|
|
/* This neighbor entry is a remote-BSS neighbor. There may be multiple remote neighbors,
|
|
* collect those in a gsm0808_cell_id_list2 (remote_target_cells). A limitation is that all of
|
|
* them need to be of the same cell id type. */
|
|
struct gsm0808_cell_id_list2 add_item;
|
|
int rc;
|
|
|
|
if (!cell_ab_match(&n->cell_id.ab, target_ab, false))
|
|
continue;
|
|
|
|
/* Convert the gsm0808_cell_id to a list, so that we can use gsm0808_cell_id_list_add(). */
|
|
gsm0808_cell_id_to_list(&add_item, &n->cell_id.id);
|
|
rc = gsm0808_cell_id_list_add(&remotes, &add_item);
|
|
if (rc < 0) {
|
|
if (log_errors)
|
|
LOGP(DHO, LOGL_ERROR, "Neighbor config error:"
|
|
" Local BTS %d -> %s resolves to remote-BSS neighbor %s;"
|
|
" Could not store this in neighbors list %s\n",
|
|
from_bts->nr, cell_ab_to_str_c(OTC_SELECT, target_ab),
|
|
gsm0808_cell_id_name_c(OTC_SELECT, &n->cell_id.id),
|
|
gsm0808_cell_id_list_name_c(OTC_SELECT, &remotes));
|
|
return rc;
|
|
}
|
|
}
|
|
/* else: neighbor entry that does not resolve to anything. */
|
|
}
|
|
|
|
if (local_neighbor_p)
|
|
*local_neighbor_p = local_neighbor;
|
|
if (remote_neighbors)
|
|
*remote_neighbors = remotes;
|
|
|
|
if (!local_neighbor && !remotes.id_list_len)
|
|
return -ENOENT;
|
|
return 0;
|
|
}
|
|
|
|
int cell_ab_to_str_buf(char *buf, size_t buflen, const struct cell_ab *cell)
|
|
{
|
|
struct osmo_strbuf sb = { .buf = buf, .len = buflen };
|
|
OSMO_STRBUF_PRINTF(sb, "ARFCN-BSIC:%u", cell->arfcn);
|
|
if (cell->bsic == BSIC_ANY)
|
|
OSMO_STRBUF_PRINTF(sb, "-any");
|
|
else {
|
|
OSMO_STRBUF_PRINTF(sb, "-%u", cell->bsic);
|
|
if (cell->bsic > 0x3f)
|
|
OSMO_STRBUF_PRINTF(sb, "[ERANGE>63]");
|
|
}
|
|
return sb.chars_needed;
|
|
}
|
|
|
|
char *cell_ab_to_str_c(void *ctx, const struct cell_ab *cell)
|
|
{
|
|
OSMO_NAME_C_IMPL(ctx, 64, "ERROR", cell_ab_to_str_buf, cell)
|
|
}
|
|
|
|
int neighbor_to_str_buf(char *buf, size_t buflen, const struct neighbor *n)
|
|
{
|
|
struct osmo_strbuf sb = { .buf = buf, .len = buflen };
|
|
switch (n->type) {
|
|
case NEIGHBOR_TYPE_BTS_NR:
|
|
OSMO_STRBUF_PRINTF(sb, "BTS %u", n->bts_nr);
|
|
break;
|
|
case NEIGHBOR_TYPE_CELL_ID:
|
|
OSMO_STRBUF_APPEND_NOLEN(sb, gsm0808_cell_id_name_buf, &n->cell_id.id);
|
|
if (n->cell_id.ab_present) {
|
|
OSMO_STRBUF_PRINTF(sb, " ");
|
|
OSMO_STRBUF_APPEND(sb, cell_ab_to_str_buf, &n->cell_id.ab);
|
|
}
|
|
break;
|
|
case NEIGHBOR_TYPE_UNSET:
|
|
OSMO_STRBUF_PRINTF(sb, "UNSET");
|
|
break;
|
|
default:
|
|
OSMO_STRBUF_PRINTF(sb, "INVALID");
|
|
break;
|
|
}
|
|
return sb.chars_needed;
|
|
}
|
|
|
|
char *neighbor_to_str_c(void *ctx, const struct neighbor *n)
|
|
{
|
|
OSMO_NAME_C_IMPL(ctx, 64, "ERROR", neighbor_to_str_buf, n);
|
|
}
|
|
|
|
bool neighbor_same(const struct neighbor *a, const struct neighbor *b, bool check_cell_ab)
|
|
{
|
|
if (a == b)
|
|
return true;
|
|
if (a->type != b->type)
|
|
return false;
|
|
|
|
switch (a->type) {
|
|
case NEIGHBOR_TYPE_BTS_NR:
|
|
return a->bts_nr == b->bts_nr;
|
|
|
|
case NEIGHBOR_TYPE_CELL_ID:
|
|
if (check_cell_ab
|
|
&& (a->cell_id.ab_present != b->cell_id.ab_present
|
|
|| !cell_ab_match(&a->cell_id.ab, &b->cell_id.ab, true)))
|
|
return false;
|
|
return gsm0808_cell_ids_match(&a->cell_id.id, &b->cell_id.id, true);
|
|
default:
|
|
return a->type == b->type;
|
|
}
|
|
}
|
|
|
|
/* Return true when the entry matches the search_for requirements.
|
|
* If exact_match is false, a BSIC_ANY entry acts as wildcard to match any search_for on that ARFCN,
|
|
* and a BSIC_ANY in search_for likewise returns any one entry that matches the ARFCN.
|
|
* If exact_match is true, only identical bsic values return a match.
|
|
* Note, typically wildcard BSICs are only in entry, e.g. the user configured list, and search_for
|
|
* contains a specific BSIC, e.g. as received from a Measurement Report. */
|
|
bool cell_ab_match(const struct cell_ab *entry,
|
|
const struct cell_ab *search_for,
|
|
bool exact_match)
|
|
{
|
|
if (entry->arfcn != search_for->arfcn)
|
|
return false;
|
|
|
|
if (exact_match && entry->bsic != search_for->bsic)
|
|
return false;
|
|
|
|
if (entry->bsic == BSIC_ANY || search_for->bsic == BSIC_ANY)
|
|
return true;
|
|
|
|
return entry->bsic == search_for->bsic;
|
|
}
|
|
|
|
bool cell_ab_valid(const struct cell_ab *cell)
|
|
{
|
|
if (cell->bsic != BSIC_ANY && cell->bsic > 0x3f)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
int neighbors_check_cfg(void)
|
|
{
|
|
/* A local neighbor can be configured by BTS number, or by a cell ID. A local neighbor can omit the ARFCN+BSIC,
|
|
* in which case those are taken from that local BTS config. If a local neighbor has ARFCN+BSIC configured, it
|
|
* must match the local cell's configuration.
|
|
*
|
|
* A remote neighbor must always be a cell ID *and* ARFCN+BSIC.
|
|
*
|
|
* Hence any cell ID with ARFCN+BSIC where the cell ID is not found among the local cells is a remote-BSS
|
|
* neighbor.
|
|
*/
|
|
struct gsm_bts *bts;
|
|
bool ok = true;
|
|
|
|
llist_for_each_entry(bts, &bsc_gsmnet->bts_list, list) {
|
|
struct neighbor *neighbor;
|
|
struct gsm_bts *local_neighbor;
|
|
llist_for_each_entry(neighbor, &bts->neighbors, entry) {
|
|
switch (neighbor->type) {
|
|
|
|
case NEIGHBOR_TYPE_BTS_NR:
|
|
if (!gsm_bts_num(bsc_gsmnet, neighbor->bts_nr)) {
|
|
LOGP(DHO, LOGL_ERROR, "Neighbor Configuration Error:"
|
|
" BTS %u -> BTS %u: There is no BTS nr %u\n",
|
|
bts->nr, neighbor->bts_nr, neighbor->bts_nr);
|
|
ok = false;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
switch (resolve_local_neighbor(&local_neighbor, bts, neighbor)) {
|
|
case 0:
|
|
break;
|
|
case -ENOENT:
|
|
if (!neighbor->cell_id.ab_present) {
|
|
LOGP(DHO, LOGL_ERROR, "Neighbor Configuration Error:"
|
|
" BTS %u -> %s: There is no such local neighbor\n",
|
|
bts->nr, neighbor_to_str_c(OTC_SELECT, neighbor));
|
|
ok = false;
|
|
}
|
|
break;
|
|
default:
|
|
/* Error already logged in resolve_local_neighbor() */
|
|
ok = false;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!ok)
|
|
return -EINVAL;
|
|
return 0;
|
|
}
|
|
|
|
/* Neighbor Resolution CTRL iface */
|
|
|
|
CTRL_CMD_DEFINE_RO(neighbor_resolve_cgi_ps_from_lac_ci, "neighbor_resolve_cgi_ps_from_lac_ci");
|
|
|
|
static int gsm_bts_get_cgi_ps(const struct gsm_bts *bts, struct osmo_cell_global_id_ps *cgi_ps)
|
|
{
|
|
if (bts->gprs.mode == BTS_GPRS_NONE)
|
|
return -ENOTSUP;
|
|
|
|
cgi_ps->rai.lac.plmn = bts->network->plmn;
|
|
cgi_ps->rai.lac.lac = bts->location_area_code;
|
|
cgi_ps->rai.rac = bts->gprs.rac;
|
|
cgi_ps->cell_identity = bts->cell_identity;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Attempt resolution of cgi_ps from ARFCN+BSIC of neighbor from BTS identified by LAC+CI */
|
|
int neighbor_address_resolution(const struct gsm_network *net, const struct cell_ab *ab,
|
|
uint16_t lac, uint16_t cell_id,
|
|
struct osmo_cell_global_id_ps *res_cgi_ps)
|
|
{
|
|
struct gsm_bts *bts_tmp, *bts_found = NULL;
|
|
struct osmo_cell_global_id_ps local_cgi_ps;
|
|
const struct osmo_cell_global_id_ps *cgi_ps = NULL;
|
|
struct gsm_bts *local_neighbor = NULL;
|
|
struct gsm0808_cell_id_list2 remote_neighbors = { 0 };
|
|
|
|
llist_for_each_entry(bts_tmp, &net->bts_list, list) {
|
|
if (bts_tmp->location_area_code != lac)
|
|
continue;
|
|
if (bts_tmp->cell_identity != cell_id)
|
|
continue;
|
|
bts_found = bts_tmp;
|
|
break;
|
|
}
|
|
|
|
if (!bts_found)
|
|
goto notfound_err;
|
|
|
|
LOG_BTS(bts_found, DLINP, LOGL_DEBUG, "Resolving neighbor BTS %u -> %s\n", bts_found->nr,
|
|
cell_ab_to_str_c(OTC_SELECT, ab));
|
|
|
|
if (resolve_neighbors(&local_neighbor, &remote_neighbors, bts_found, ab, true))
|
|
goto notfound_err;
|
|
|
|
/* resolve_neighbors() returns either a local_neighbor or remote_neighbors.
|
|
* Local-BSS neighbor? */
|
|
if (local_neighbor) {
|
|
/* Supporting GPRS? */
|
|
if (gsm_bts_get_cgi_ps(local_neighbor, &local_cgi_ps) >= 0)
|
|
cgi_ps = &local_cgi_ps;
|
|
}
|
|
|
|
/* Remote-BSS neighbor?
|
|
* By spec, there can be multiple remote neighbors for a given ARFCN+BSIC, but so far osmo-bsc enforces only a
|
|
* single remote neighbor. */
|
|
if (remote_neighbors.id_list_len
|
|
&& remote_neighbors.id_discr == CELL_IDENT_WHOLE_GLOBAL_PS) {
|
|
cgi_ps = &remote_neighbors.id_list[0].global_ps;
|
|
}
|
|
|
|
/* No neighbor found */
|
|
if (!cgi_ps)
|
|
goto notfound_err;
|
|
|
|
*res_cgi_ps = *cgi_ps;
|
|
return 0;
|
|
|
|
notfound_err:
|
|
return -1;
|
|
}
|
|
|
|
static int get_neighbor_resolve_cgi_ps_from_lac_ci(struct ctrl_cmd *cmd, void *data)
|
|
{
|
|
struct gsm_network *net = (struct gsm_network *)data;
|
|
char *tmp = NULL, *tok, *saveptr;
|
|
struct cell_ab ab;
|
|
unsigned int lac, cell_id;
|
|
struct osmo_cell_global_id_ps cgi_ps;
|
|
|
|
if (!cmd->variable)
|
|
goto fmt_err;
|
|
|
|
tmp = talloc_strdup(cmd, cmd->variable);
|
|
if (!tmp) {
|
|
cmd->reply = "OOM";
|
|
return CTRL_CMD_ERROR;
|
|
}
|
|
|
|
if (!(tok = strtok_r(tmp, ".", &saveptr)))
|
|
goto fmt_err;
|
|
OSMO_ASSERT(strcmp(tok, "neighbor_resolve_cgi_ps_from_lac_ci") == 0);
|
|
|
|
if (!(tok = strtok_r(NULL, ".", &saveptr)))
|
|
goto fmt_err;
|
|
lac = atoi(tok);
|
|
|
|
if (!(tok = strtok_r(NULL, ".", &saveptr)))
|
|
goto fmt_err;
|
|
cell_id = atoi(tok);
|
|
|
|
if (!(tok = strtok_r(NULL, ".", &saveptr)))
|
|
goto fmt_err;
|
|
ab.arfcn = atoi(tok);
|
|
|
|
if (!(tok = strtok_r(NULL, "\0", &saveptr)))
|
|
goto fmt_err;
|
|
ab.bsic = atoi(tok);
|
|
|
|
if (!cell_ab_valid(&ab))
|
|
goto fmt_err;
|
|
|
|
if (neighbor_address_resolution(net, &ab, lac, cell_id, &cgi_ps) < 0)
|
|
goto notfound_err;
|
|
|
|
ctrl_cmd_reply_printf(cmd, "%s", osmo_cgi_ps_name(&cgi_ps));
|
|
talloc_free(tmp);
|
|
return CTRL_CMD_REPLY;
|
|
|
|
notfound_err:
|
|
talloc_free(tmp);
|
|
cmd->reply = talloc_strdup(cmd, "No target CGI PS found");
|
|
return CTRL_CMD_ERROR;
|
|
fmt_err:
|
|
talloc_free(tmp);
|
|
cmd->reply = talloc_strdup(cmd, "The format is <src_lac>,<src_cell_id>,<dst_arfcn>,<dst_bsic>");
|
|
return CTRL_CMD_ERROR;
|
|
}
|
|
|
|
int neighbor_ctrl_cmds_install(struct gsm_network *net)
|
|
{
|
|
int rc;
|
|
|
|
rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_neighbor_resolve_cgi_ps_from_lac_ci);
|
|
return rc;
|
|
}
|
|
|
|
struct ctrl_handle *neighbor_controlif_setup(struct gsm_network *net)
|
|
{
|
|
/* DEPRECATED: see osmobsc-usermanual.pdf, section 16.1.1 Neighbor Address Resolution Service */
|
|
return ctrl_interface_setup_dynip2(net, net->neigh_ctrl.addr, net->neigh_ctrl.port,
|
|
NULL, _LAST_CTRL_NODE_NEIGHBOR);
|
|
}
|