osmo-bsc/src/osmo-bsc/neighbor_ident.c

496 lines
14 KiB
C
Raw Normal View History

/* 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;
}
Support Neighbor Address Resolution over PCUIF IPA multiplex While NACC was initially developed, it became clear there was need for a way to interact PCU<->BSC in order resolve ARFCN+BSIC into CGI-PS for later RIM usage. Hence, this resolution was first (until today) implemented using an out of bands RPC system using the CTRL interface, which required specific config to be written and matches in osmo-pcu and osmo-bsc VTY (ip+port of the CTRL interface to use). However, this has several shortcomings: * As explained above, specific configuration is required * Since recently, we do support BSC redundancy in osmo-bts. Hence the BTS may switch to a BSC other than first one. If that happened, that'd mean the CTRL interface would still point to the initially configured one, which may not be the same currently serving the PCU. During recent development of ANR related features, a similar need for PCU<->BSC was required, but this time it was decided to extend the IPA multiplex of the Abis OML connection to pass PCUIF messages, transparently forwarded to each side by the BTS. This has the advantage that connection PCU<->BTS is handled by BTS and both sides send messages transparently. Let's switch by default to using this new interface, while still maintaing the old way for a while (announcing them as deprecated) to avoid breaking existing deployments until they are upgraded to new versions of osmo-pcu and osmo-bsc. Change-Id: I9073a121564503f483c84263ac72476041e47c03 Related: SYS#4971
2021-09-07 15:56:47 +00:00
/* 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,
Support Neighbor Address Resolution over PCUIF IPA multiplex While NACC was initially developed, it became clear there was need for a way to interact PCU<->BSC in order resolve ARFCN+BSIC into CGI-PS for later RIM usage. Hence, this resolution was first (until today) implemented using an out of bands RPC system using the CTRL interface, which required specific config to be written and matches in osmo-pcu and osmo-bsc VTY (ip+port of the CTRL interface to use). However, this has several shortcomings: * As explained above, specific configuration is required * Since recently, we do support BSC redundancy in osmo-bts. Hence the BTS may switch to a BSC other than first one. If that happened, that'd mean the CTRL interface would still point to the initially configured one, which may not be the same currently serving the PCU. During recent development of ANR related features, a similar need for PCU<->BSC was required, but this time it was decided to extend the IPA multiplex of the Abis OML connection to pass PCUIF messages, transparently forwarded to each side by the BTS. This has the advantage that connection PCU<->BTS is handled by BTS and both sides send messages transparently. Let's switch by default to using this new interface, while still maintaing the old way for a while (announcing them as deprecated) to avoid breaking existing deployments until they are upgraded to new versions of osmo-pcu and osmo-bsc. Change-Id: I9073a121564503f483c84263ac72476041e47c03 Related: SYS#4971
2021-09-07 15:56:47 +00:00
cell_ab_to_str_c(OTC_SELECT, ab));
Support Neighbor Address Resolution over PCUIF IPA multiplex While NACC was initially developed, it became clear there was need for a way to interact PCU<->BSC in order resolve ARFCN+BSIC into CGI-PS for later RIM usage. Hence, this resolution was first (until today) implemented using an out of bands RPC system using the CTRL interface, which required specific config to be written and matches in osmo-pcu and osmo-bsc VTY (ip+port of the CTRL interface to use). However, this has several shortcomings: * As explained above, specific configuration is required * Since recently, we do support BSC redundancy in osmo-bts. Hence the BTS may switch to a BSC other than first one. If that happened, that'd mean the CTRL interface would still point to the initially configured one, which may not be the same currently serving the PCU. During recent development of ANR related features, a similar need for PCU<->BSC was required, but this time it was decided to extend the IPA multiplex of the Abis OML connection to pass PCUIF messages, transparently forwarded to each side by the BTS. This has the advantage that connection PCU<->BTS is handled by BTS and both sides send messages transparently. Let's switch by default to using this new interface, while still maintaing the old way for a while (announcing them as deprecated) to avoid breaking existing deployments until they are upgraded to new versions of osmo-pcu and osmo-bsc. Change-Id: I9073a121564503f483c84263ac72476041e47c03 Related: SYS#4971
2021-09-07 15:56:47 +00:00
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;
Support Neighbor Address Resolution over PCUIF IPA multiplex While NACC was initially developed, it became clear there was need for a way to interact PCU<->BSC in order resolve ARFCN+BSIC into CGI-PS for later RIM usage. Hence, this resolution was first (until today) implemented using an out of bands RPC system using the CTRL interface, which required specific config to be written and matches in osmo-pcu and osmo-bsc VTY (ip+port of the CTRL interface to use). However, this has several shortcomings: * As explained above, specific configuration is required * Since recently, we do support BSC redundancy in osmo-bts. Hence the BTS may switch to a BSC other than first one. If that happened, that'd mean the CTRL interface would still point to the initially configured one, which may not be the same currently serving the PCU. During recent development of ANR related features, a similar need for PCU<->BSC was required, but this time it was decided to extend the IPA multiplex of the Abis OML connection to pass PCUIF messages, transparently forwarded to each side by the BTS. This has the advantage that connection PCU<->BTS is handled by BTS and both sides send messages transparently. Let's switch by default to using this new interface, while still maintaing the old way for a while (announcing them as deprecated) to avoid breaking existing deployments until they are upgraded to new versions of osmo-pcu and osmo-bsc. Change-Id: I9073a121564503f483c84263ac72476041e47c03 Related: SYS#4971
2021-09-07 15:56:47 +00:00
*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);
}