diff options
author | Neels Hofmeyr <neels@hofmeyr.de> | 2018-03-22 04:54:57 +0100 |
---|---|---|
committer | Neels Hofmeyr <neels@hofmeyr.de> | 2018-06-25 23:56:28 +0200 |
commit | 5708800e46aab047712eacd34fc57bd0df0f4e1e (patch) | |
tree | 0d5738ac4a2591684f4d0eec7134f8471fdfefb8 /src/osmo-bsc | |
parent | 9eea6a9d0584c3f0a7f12fd7af55308e6546f07b (diff) |
inter-BSC HO: add neighbor_ident API to manage neighbor-BSS-cells
Change-Id: I0153d7069817fba9146ddc11214de2757d7d37bf
Diffstat (limited to 'src/osmo-bsc')
-rw-r--r-- | src/osmo-bsc/Makefile.am | 2 | ||||
-rw-r--r-- | src/osmo-bsc/bsc_init.c | 1 | ||||
-rw-r--r-- | src/osmo-bsc/bsc_vty.c | 5 | ||||
-rw-r--r-- | src/osmo-bsc/gsm_data.c | 113 | ||||
-rw-r--r-- | src/osmo-bsc/handover_logic.c | 44 | ||||
-rw-r--r-- | src/osmo-bsc/neighbor_ident.c | 296 | ||||
-rw-r--r-- | src/osmo-bsc/neighbor_ident_vty.c | 561 | ||||
-rw-r--r-- | src/osmo-bsc/net_init.c | 1 | ||||
-rw-r--r-- | src/osmo-bsc/system_information.c | 51 |
9 files changed, 1069 insertions, 5 deletions
diff --git a/src/osmo-bsc/Makefile.am b/src/osmo-bsc/Makefile.am index a459a928b..afae0b66b 100644 --- a/src/osmo-bsc/Makefile.am +++ b/src/osmo-bsc/Makefile.am @@ -64,6 +64,8 @@ osmo_bsc_SOURCES = \ handover_vty.c \ meas_feed.c \ meas_rep.c \ + neighbor_ident.c \ + neighbor_ident_vty.c \ net_init.c \ osmo_bsc_api.c \ osmo_bsc_audio.c \ diff --git a/src/osmo-bsc/bsc_init.c b/src/osmo-bsc/bsc_init.c index b6bd41025..1fe484761 100644 --- a/src/osmo-bsc/bsc_init.c +++ b/src/osmo-bsc/bsc_init.c @@ -247,6 +247,7 @@ static struct gsm_network *bsc_network_init(void *ctx) net->ho = ho_cfg_init(net, NULL); net->hodec2.congestion_check_interval_s = HO_CFG_CONGESTION_CHECK_DEFAULT; + net->neighbor_bss_cells = neighbor_ident_init(net); /* init statistics */ net->bsc_ctrs = rate_ctr_group_alloc(net, &bsc_ctrg_desc, 0); diff --git a/src/osmo-bsc/bsc_vty.c b/src/osmo-bsc/bsc_vty.c index 57c536323..f25f73106 100644 --- a/src/osmo-bsc/bsc_vty.c +++ b/src/osmo-bsc/bsc_vty.c @@ -62,6 +62,8 @@ #include <osmocom/bsc/gsm_04_08_utils.h> #include <osmocom/bsc/acc_ramp.h> #include <osmocom/bsc/meas_feed.h> +#include <osmocom/bsc/neighbor_ident.h> +#include <osmocom/bsc/handover.h> #include <inttypes.h> @@ -909,6 +911,8 @@ static void config_write_bts_single(struct vty *vty, struct gsm_bts *bts) VTY_NEWLINE); } + neighbor_ident_vty_write(vty, " ", bts); + vty_out(vty, " codec-support fr"); if (bts->codec.hr) vty_out(vty, " hr"); @@ -4967,6 +4971,7 @@ int bsc_vty_init(struct gsm_network *network) install_element(BTS_NODE, &cfg_bts_no_acc_ramping_cmd); install_element(BTS_NODE, &cfg_bts_acc_ramping_step_interval_cmd); install_element(BTS_NODE, &cfg_bts_acc_ramping_step_size_cmd); + neighbor_ident_vty_init(network, network->neighbor_bss_cells); /* See also handover commands added on bts level from handover_vty.c */ install_element(BTS_NODE, &cfg_trx_cmd); diff --git a/src/osmo-bsc/gsm_data.c b/src/osmo-bsc/gsm_data.c index 0f062d25a..734e2fb6f 100644 --- a/src/osmo-bsc/gsm_data.c +++ b/src/osmo-bsc/gsm_data.c @@ -33,6 +33,7 @@ #include <osmocom/core/statistics.h> #include <osmocom/gsm/protocol/gsm_04_08.h> #include <osmocom/gsm/gsm48.h> +#include <osmocom/gsm/gsm0808_utils.h> #include <osmocom/bsc/gsm_data.h> #include <osmocom/bsc/bsc_msc_data.h> @@ -563,6 +564,117 @@ struct gsm_bts *gsm_bts_num(struct gsm_network *net, int num) return NULL; } +bool gsm_bts_matches_cell_id(struct gsm_bts *bts, const struct gsm0808_cell_id *ci) +{ + if (!bts || !ci) + return false; + switch (ci->id_discr) { + case CELL_IDENT_WHOLE_GLOBAL: + if (osmo_plmn_cmp(&bts->network->plmn, &ci->id.global.lai.plmn)) + return false; + if (bts->location_area_code != ci->id.global.lai.lac) + return false; + if (bts->cell_identity != ci->id.global.cell_identity) + return false; + return true; + case CELL_IDENT_LAC_AND_CI: + if (bts->location_area_code != ci->id.lac_and_ci.lac) + return false; + if (bts->cell_identity != ci->id.lac_and_ci.ci) + return false; + return true; + case CELL_IDENT_CI: + if (bts->cell_identity != ci->id.ci) + return false; + return true; + case CELL_IDENT_NO_CELL: + return false; + case CELL_IDENT_LAI_AND_LAC: + if (osmo_plmn_cmp(&bts->network->plmn, &ci->id.lai_and_lac.plmn)) + return false; + if (bts->location_area_code != ci->id.lai_and_lac.lac) + return false; + return true; + case CELL_IDENT_LAC: + if (bts->location_area_code != ci->id.lac) + return false; + return true; + case CELL_IDENT_BSS: + return true; + case CELL_IDENT_UTRAN_PLMN_LAC_RNC: + case CELL_IDENT_UTRAN_RNC: + case CELL_IDENT_UTRAN_LAC_RNC: + /* Not implemented */ + default: + return false; + } +} + +struct gsm_bts *gsm_bts_by_cell_id(struct gsm_network *net, const struct gsm0808_cell_id *ci) +{ + struct gsm_bts *bts; + + llist_for_each_entry(bts, &net->bts_list, list) { + if (gsm_bts_matches_cell_id(bts, ci)) + return bts; + } + + return NULL; +} + +struct gsm_bts_ref *gsm_bts_ref_find(const struct llist_head *list, const struct gsm_bts *bts) +{ + struct gsm_bts_ref *ref; + if (!bts) + return NULL; + llist_for_each_entry(ref, list, entry) { + if (ref->bts == bts) + return ref; + } + return NULL; +} + +/* Add a BTS reference to the local_neighbors list. + * Return 1 if added, 0 if such an entry already existed, and negative on errors. */ +int gsm_bts_local_neighbor_add(struct gsm_bts *bts, struct gsm_bts *neighbor) +{ + struct gsm_bts_ref *ref; + if (!bts || !neighbor) + return -ENOMEM; + + if (bts == neighbor) + return -EINVAL; + + /* Already got this entry? */ + ref = gsm_bts_ref_find(&bts->local_neighbors, neighbor); + if (ref) + return 0; + + ref = talloc_zero(bts, struct gsm_bts_ref); + if (!ref) + return -ENOMEM; + ref->bts = neighbor; + llist_add_tail(&ref->entry, &bts->local_neighbors); + return 1; +} + +/* Remove a BTS reference from the local_neighbors list. + * Return 1 if removed, 0 if no such entry existed, and negative on errors. */ +int gsm_bts_local_neighbor_del(struct gsm_bts *bts, const struct gsm_bts *neighbor) +{ + struct gsm_bts_ref *ref; + if (!bts || !neighbor) + return -ENOMEM; + + ref = gsm_bts_ref_find(&bts->local_neighbors, neighbor); + if (!ref) + return 0; + + llist_del(&ref->entry); + talloc_free(ref); + return 1; +} + struct gsm_bts_trx *gsm_bts_trx_alloc(struct gsm_bts *bts) { struct gsm_bts_trx *trx = talloc_zero(bts, struct gsm_bts_trx); @@ -756,6 +868,7 @@ struct gsm_bts *gsm_bts_alloc(struct gsm_network *net, uint8_t bts_num) INIT_LLIST_HEAD(&bts->abis_queue); INIT_LLIST_HEAD(&bts->loc_list); + INIT_LLIST_HEAD(&bts->local_neighbors); return bts; } diff --git a/src/osmo-bsc/handover_logic.c b/src/osmo-bsc/handover_logic.c index 960bf6993..55af0ed50 100644 --- a/src/osmo-bsc/handover_logic.c +++ b/src/osmo-bsc/handover_logic.c @@ -392,6 +392,50 @@ static int ho_meas_rep(struct gsm_meas_rep *mr) return 0; } +struct gsm_bts *bts_by_neighbor_ident(const struct gsm_network *net, + const struct neighbor_ident_key *search_for) +{ + struct gsm_bts *found = NULL; + struct gsm_bts *bts; + struct gsm_bts *wildcard_match = NULL; + + llist_for_each_entry(bts, &net->bts_list, list) { + struct neighbor_ident_key entry = { + .from_bts = NEIGHBOR_IDENT_KEY_ANY_BTS, + .arfcn = bts->c0->arfcn, + .bsic_kind = BSIC_6BIT, + .bsic = bts->bsic, + }; + if (neighbor_ident_key_match(&entry, search_for, true)) { + if (found) { + LOGP(DHO, LOGL_ERROR, "CONFIG ERROR: Multiple BTS match %s: %d and %d\n", + neighbor_ident_key_name(search_for), + found->nr, bts->nr); + return found; + } + found = bts; + } + if (neighbor_ident_key_match(&entry, search_for, false)) + wildcard_match = bts; + } + + if (found) + return found; + + return wildcard_match; +} + +struct neighbor_ident_key *bts_ident_key(const struct gsm_bts *bts) +{ + static struct neighbor_ident_key key; + key = (struct neighbor_ident_key){ + .arfcn = bts->c0->arfcn, + .bsic_kind = BSIC_6BIT, + .bsic = bts->bsic, + }; + return &key; +} + static int ho_logic_sig_cb(unsigned int subsys, unsigned int signal, void *handler_data, void *signal_data) { diff --git a/src/osmo-bsc/neighbor_ident.c b/src/osmo-bsc/neighbor_ident.c new file mode 100644 index 000000000..8a7c580ae --- /dev/null +++ b/src/osmo-bsc/neighbor_ident.c @@ -0,0 +1,296 @@ +/* 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> + +struct neighbor_ident_list { + struct llist_head list; +}; + +struct neighbor_ident { + struct llist_head entry; + + struct neighbor_ident_key key; + struct gsm0808_cell_id_list2 val; +}; + +#define APPEND_THING(func, args...) do { \ + int remain = buflen - (pos - buf); \ + int l = func(pos, remain, ##args); \ + if (l < 0 || l > remain) \ + pos = buf + buflen; \ + else \ + pos += l; \ + } while(0) +#define APPEND_STR(fmt, args...) APPEND_THING(snprintf, fmt, ##args) + +const char *_neighbor_ident_key_name(char *buf, size_t buflen, const struct neighbor_ident_key *ni_key) +{ + char *pos = buf; + + APPEND_STR("BTS "); + if (ni_key->from_bts == NEIGHBOR_IDENT_KEY_ANY_BTS) + APPEND_STR("*"); + else if (ni_key->from_bts >= 0 && ni_key->from_bts <= 255) + APPEND_STR("%d", ni_key->from_bts); + else + APPEND_STR("invalid(%d)", ni_key->from_bts); + + APPEND_STR(" to "); + switch (ni_key->bsic_kind) { + default: + case BSIC_NONE: + APPEND_STR("ARFCN %u (any BSIC)", ni_key->arfcn); + break; + case BSIC_6BIT: + APPEND_STR("ARFCN %u BSIC %u", ni_key->arfcn, ni_key->bsic & 0x3f); + break; + case BSIC_9BIT: + APPEND_STR("ARFCN %u BSIC %u(9bit)", ni_key->arfcn, ni_key->bsic & 0x1ff); + break; + } + return buf; +} + +const char *neighbor_ident_key_name(const struct neighbor_ident_key *ni_key) +{ + static char buf[64]; + return _neighbor_ident_key_name(buf, sizeof(buf), ni_key); +} + +struct neighbor_ident_list *neighbor_ident_init(void *talloc_ctx) +{ + struct neighbor_ident_list *nil = talloc_zero(talloc_ctx, struct neighbor_ident_list); + OSMO_ASSERT(nil); + INIT_LLIST_HEAD(&nil->list); + return nil; +} + +void neighbor_ident_free(struct neighbor_ident_list *nil) +{ + if (!nil) + return; + talloc_free(nil); +} + +/* Return true when the entry matches the search_for requirements. + * If exact_match is false, a BSIC_NONE entry acts as wildcard to match any search_for on that ARFCN, + * and a BSIC_NONE in search_for likewise returns any one entry that matches the ARFCN; + * also a from_bts == NEIGHBOR_IDENT_KEY_ANY_BTS in either entry or search_for will match. + * If exact_match is true, only identical bsic_kind values and identical from_bts 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 neighbor_ident_key_match(const struct neighbor_ident_key *entry, + const struct neighbor_ident_key *search_for, + bool exact_match) +{ + uint16_t bsic_mask; + + if (exact_match + && entry->from_bts != search_for->from_bts) + return false; + + if (search_for->from_bts != NEIGHBOR_IDENT_KEY_ANY_BTS + && entry->from_bts != NEIGHBOR_IDENT_KEY_ANY_BTS + && entry->from_bts != search_for->from_bts) + return false; + + if (entry->arfcn != search_for->arfcn) + return false; + + switch (entry->bsic_kind) { + default: + return false; + case BSIC_NONE: + if (!exact_match) { + /* The neighbor identifier list entry matches any BSIC for this ARFCN. */ + return true; + } + /* Match exact entry */ + bsic_mask = 0; + break; + case BSIC_6BIT: + bsic_mask = 0x3f; + break; + case BSIC_9BIT: + bsic_mask = 0x1ff; + break; + } + if (!exact_match && search_for->bsic_kind == BSIC_NONE) { + /* The search is looking only for an ARFCN with any BSIC */ + return true; + } + if (search_for->bsic_kind == entry->bsic_kind + && (search_for->bsic & bsic_mask) == (entry->bsic & bsic_mask)) + return true; + return false; +} + +static struct neighbor_ident *_neighbor_ident_get(const struct neighbor_ident_list *nil, + const struct neighbor_ident_key *key, + bool exact_match) +{ + struct neighbor_ident *ni; + struct neighbor_ident *wildcard_match = NULL; + + /* Do both exact-bsic and wildcard matching in the same iteration: + * Any exact match returns immediately, while for a wildcard match we still go through all + * remaining items in case an exact match exists. */ + llist_for_each_entry(ni, &nil->list, entry) { + if (neighbor_ident_key_match(&ni->key, key, true)) + return ni; + if (!exact_match) { + if (neighbor_ident_key_match(&ni->key, key, false)) + wildcard_match = ni; + } + } + return wildcard_match; +} + +static void _neighbor_ident_free(struct neighbor_ident *ni) +{ + llist_del(&ni->entry); + talloc_free(ni); +} + +bool neighbor_ident_key_valid(const struct neighbor_ident_key *key) +{ + if (key->from_bts != NEIGHBOR_IDENT_KEY_ANY_BTS + && (key->from_bts < 0 || key->from_bts > 255)) + return false; + + switch (key->bsic_kind) { + case BSIC_6BIT: + if (key->bsic > 0x3f) + return false; + break; + case BSIC_9BIT: + if (key->bsic > 0x1ff) + return false; + break; + case BSIC_NONE: + break; + default: + return false; + } + return true; +} + +/*! Add Cell Identifiers to an ARFCN+BSIC entry. + * Exactly one kind of identifier is allowed per ARFCN+BSIC entry, and any number of entries of that kind + * may be added up to the capacity of gsm0808_cell_id_list2, by one or more calls to this function. To + * replace an existing entry, first call neighbor_ident_del(nil, key). + * \returns number of entries in the resulting identifier list, or negative on error: + * see gsm0808_cell_id_list_add() for the meaning of returned error codes; + * return -ENOMEM when the list is not initialized, -ERANGE when the BSIC value is too large. */ +int neighbor_ident_add(struct neighbor_ident_list *nil, const struct neighbor_ident_key *key, + const struct gsm0808_cell_id_list2 *val) +{ + struct neighbor_ident *ni; + int rc; + + if (!nil) + return -ENOMEM; + + if (!neighbor_ident_key_valid(key)) + return -ERANGE; + + ni = _neighbor_ident_get(nil, key, true); + if (!ni) { + ni = talloc_zero(nil, struct neighbor_ident); + OSMO_ASSERT(ni); + *ni = (struct neighbor_ident){ + .key = *key, + .val = *val, + }; + llist_add_tail(&ni->entry, &nil->list); + return ni->val.id_list_len; + } + + rc = gsm0808_cell_id_list_add(&ni->val, val); + + if (rc < 0) + return rc; + + return ni->val.id_list_len; +} + +/*! Find cell identity for given BTS, ARFCN and BSIC, as previously added by neighbor_ident_add(). + */ +const struct gsm0808_cell_id_list2 *neighbor_ident_get(const struct neighbor_ident_list *nil, + const struct neighbor_ident_key *key) +{ + struct neighbor_ident *ni; + if (!nil) + return NULL; + ni = _neighbor_ident_get(nil, key, false); + if (!ni) + return NULL; + return &ni->val; +} + +bool neighbor_ident_del(struct neighbor_ident_list *nil, const struct neighbor_ident_key *key) +{ + struct neighbor_ident *ni; + if (!nil) + return false; + ni = _neighbor_ident_get(nil, key, true); + if (!ni) + return false; + _neighbor_ident_free(ni); + return true; +} + +void neighbor_ident_clear(struct neighbor_ident_list *nil) +{ + struct neighbor_ident *ni; + while ((ni = llist_first_entry_or_null(&nil->list, struct neighbor_ident, entry))) + _neighbor_ident_free(ni); +} + +/*! Iterate all neighbor_ident_list entries and call iter_cb for each. + * If iter_cb returns false, the iteration is stopped. */ +void neighbor_ident_iter(const struct neighbor_ident_list *nil, + bool (* iter_cb )(const struct neighbor_ident_key *key, + const struct gsm0808_cell_id_list2 *val, + void *cb_data), + void *cb_data) +{ + struct neighbor_ident *ni, *ni_next; + if (!nil) + return; + llist_for_each_entry_safe(ni, ni_next, &nil->list, entry) { + if (!iter_cb(&ni->key, &ni->val, cb_data)) + return; + } +} diff --git a/src/osmo-bsc/neighbor_ident_vty.c b/src/osmo-bsc/neighbor_ident_vty.c new file mode 100644 index 000000000..a606ed130 --- /dev/null +++ b/src/osmo-bsc/neighbor_ident_vty.c @@ -0,0 +1,561 @@ +/* Quagga VTY implementation to manage identity of neighboring BSS cells for inter-BSC handover. */ +/* (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 <stdlib.h> +#include <string.h> +#include <errno.h> + +#include <osmocom/vty/command.h> +#include <osmocom/gsm/gsm0808.h> + +#include <osmocom/bsc/vty.h> +#include <osmocom/bsc/neighbor_ident.h> +#include <osmocom/bsc/gsm_data.h> + +static struct gsm_network *g_net = NULL; +static struct neighbor_ident_list *g_neighbor_cells = NULL; + +/* Parse VTY parameters matching NEIGHBOR_IDENT_VTY_KEY_PARAMS. Pass a pointer so that argv[0] is the + * ARFCN value followed by the BSIC keyword and value. vty *must* reference a BTS_NODE. */ +bool neighbor_ident_vty_parse_key_params(struct vty *vty, const char **argv, struct neighbor_ident_key *key) +{ + struct gsm_bts *bts = vty->index; + const char *arfcn_str = argv[0]; + const char *bsic_kind = argv[1]; + const char *bsic_str = argv[2]; + + OSMO_ASSERT(vty->node == BTS_NODE && bts); + + *key = (struct neighbor_ident_key){ + .from_bts = bts->nr, + .arfcn = atoi(arfcn_str), + }; + + if (!strcmp(bsic_str, "any")) + key->bsic_kind = BSIC_NONE; + else { + key->bsic_kind = (!strcmp(bsic_kind, "bsic9")) ? BSIC_9BIT : BSIC_6BIT; + key->bsic = atoi(bsic_str); + if (key->bsic_kind == BSIC_6BIT && key->bsic > 0x3f) { + vty_out(vty, "%% Error: BSIC value surpasses 6-bit range: %u, use 'bsic9' instead%s", + key->bsic, VTY_NEWLINE); + return false; + } + } + return true; +} + +#define NEIGHBOR_ADD_CMD "neighbor add " +#define NEIGHBOR_DEL_CMD "neighbor del " +#define NEIGHBOR_DOC "Neighbor cell list\n" +#define NEIGHBOR_ADD_DOC NEIGHBOR_DOC "Add local or remote-BSS neighbor cell\n" +#define NEIGHBOR_DEL_DOC NEIGHBOR_DOC "Remove local or remote-BSS neighbor cell\n" + +static struct gsm_bts *neighbor_ident_vty_parse_bts_nr(struct vty *vty, const char **argv) +{ + const char *bts_nr_str = argv[0]; + struct gsm_bts *bts = gsm_bts_num(g_net, atoi(bts_nr_str)); + if (!bts) + vty_out(vty, "%% No such BTS: nr = %s%s\n", bts_nr_str, VTY_NEWLINE); + return bts; +} + +static struct gsm_bts *bts_by_cell_id(struct vty *vty, struct gsm0808_cell_id *cell_id) +{ + struct gsm_bts *bts = gsm_bts_by_cell_id(g_net, cell_id); + if (!bts) + vty_out(vty, "%% No such BTS: %s%s\n", gsm0808_cell_id_name(cell_id), VTY_NEWLINE); + return bts; +} + +static struct gsm0808_cell_id *neighbor_ident_vty_parse_lac(struct vty *vty, const char **argv) +{ + static struct gsm0808_cell_id cell_id; + cell_id = (struct gsm0808_cell_id){ + .id_discr = CELL_IDENT_LAC, + .id.lac = atoi(argv[0]), + }; + return &cell_id; +} + +static struct gsm0808_cell_id *neighbor_ident_vty_parse_lac_ci(struct vty *vty, const char **argv) +{ + static struct gsm0808_cell_id cell_id; + cell_id = (struct gsm0808_cell_id){ + .id_discr = CELL_IDENT_LAC_AND_CI, + .id.lac_and_ci = { + .lac = atoi(argv[0]), + .ci = atoi(argv[1]), + }, + }; + return &cell_id; +} + +static struct gsm0808_cell_id *neighbor_ident_vty_parse_cgi(struct vty *vty, const char **argv) +{ + static struct gsm0808_cell_id cell_id; + cell_id = (struct gsm0808_cell_id){ + .id_discr = CELL_IDENT_WHOLE_GLOBAL, + }; + struct osmo_cell_global_id *cgi = &cell_id.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 NULL; + } + + 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 NULL; + } + + cgi->lai.lac = atoi(lac); + cgi->cell_identity = atoi(ci); + return &cell_id; +} + +static int add_local_bts(struct vty *vty, struct gsm_bts *neigh) +{ + int rc; + struct gsm_bts *bts = vty->index; + if (vty->node != BTS_NODE) { + vty_out(vty, "%% Error: cannot add local BTS neighbor, not on BTS node%s", + VTY_NEWLINE); + return CMD_WARNING; + } + if (!bts) { + vty_out(vty, "%% Error: cannot add local BTS neighbor, no BTS on this node%s", + VTY_NEWLINE); + return CMD_WARNING; + } + if (!neigh) { + vty_out(vty, "%% Error: cannot add local BTS neighbor to BTS %u, no such neighbor BTS%s" + "%% (To add remote-BSS neighbors, pass full ARFCN and BSIC as well)%s", + bts->nr, VTY_NEWLINE, VTY_NEWLINE); + return CMD_WARNING; + } + rc = gsm_bts_local_neighbor_add(bts, neigh); + if (rc < 0) { + vty_out(vty, "%% Error: cannot add local BTS %u as neighbor to BTS %u: %s%s", + neigh->nr, bts->nr, strerror(-rc), VTY_NEWLINE); + return CMD_WARNING; + } else + vty_out(vty, "%% BTS %u %s local neighbor BTS %u with LAC %u CI %u and ARFCN %u BSIC %u%s", + bts->nr, rc? "now has" : "already had", + neigh->nr, neigh->location_area_code, neigh->cell_identity, + neigh->c0->arfcn, neigh->bsic, VTY_NEWLINE); + return CMD_SUCCESS; +} + +static int del_local_bts(struct vty *vty, struct gsm_bts *neigh) +{ + int rc; + struct gsm_bts *bts = vty->index; + if (vty->node != BTS_NODE) { + vty_out(vty, "%% Error: cannot remove local BTS neighbor, not on BTS node%s", + VTY_NEWLINE); + return CMD_WARNING; + } + if (!bts) { + vty_out(vty, "%% Error: cannot remove local BTS neighbor, no BTS on this node%s", + VTY_NEWLINE); + return CMD_WARNING; + } + if (!neigh) { + vty_out(vty, "%% Error: cannot remove local BTS neighbor from BTS %u, no such neighbor BTS%s", + bts->nr, VTY_NEWLINE); + return CMD_WARNING; + } + rc = gsm_bts_local_neighbor_del(bts, neigh); + if (rc < 0) { + vty_out(vty, "%% Error: cannot remove local BTS %u neighbor from BTS %u: %s%s", + neigh->nr, bts->nr, strerror(-rc), VTY_NEWLINE); + return CMD_WARNING; + } + if (rc == 0) + vty_out(vty, "%% BTS %u is no neighbor of BTS %u%s", + neigh->nr, bts->nr, VTY_NEWLINE); + return CMD_SUCCESS; +} + +DEFUN(cfg_neighbor_add_bts_nr, cfg_neighbor_add_bts_nr_cmd, + NEIGHBOR_ADD_CMD "bts <0-255>", + NEIGHBOR_ADD_DOC "Neighbor cell by local BTS number\n" "BTS number\n") +{ + return add_local_bts(vty, neighbor_ident_vty_parse_bts_nr(vty, argv)); +} + +DEFUN(cfg_neighbor_add_lac, cfg_neighbor_add_lac_cmd, + NEIGHBOR_ADD_CMD "lac <0-65535>", + NEIGHBOR_ADD_DOC "Neighbor cell by LAC\n" "LAC\n") +{ + return add_local_bts(vty, bts_by_cell_id(vty, neighbor_ident_vty_parse_lac(vty, argv))); +} + +DEFUN(cfg_neighbor_add_lac_ci, cfg_neighbor_add_lac_ci_cmd, + NEIGHBOR_ADD_CMD "lac-ci <0-65535> <0-255>", + NEIGHBOR_ADD_DOC "Neighbor cell by LAC and CI\n" "LAC\n" "CI\n") +{ + return add_local_bts(vty, bts_by_cell_id(vty, neighbor_ident_vty_parse_lac_ci(vty, argv))); +} + +bool neighbor_ident_key_matches_bts(const struct neighbor_ident_key *key, struct gsm_bts *bts) +{ + if (!bts || !key) + return false; + return key->arfcn == bts->c0->arfcn + && (key->bsic_kind == BSIC_NONE || key->bsic == bts->bsic); +} + +static int add_remote_or_local_bts(struct vty *vty, const struct gsm0808_cell_id *cell_id, + const struct neighbor_ident_key *key) +{ + int rc; + struct gsm_bts *local_neigh; + struct gsm0808_cell_id_list2 cil; + struct gsm_bts *bts = vty->index; + + if (vty->node != BTS_NODE) { + vty_out(vty, "%% Error: cannot add BTS neighbor, not on BTS node%s", + VTY_NEWLINE); + return CMD_WARNING; + } + if (!bts) { + vty_out(vty, "%% Error: cannot add BTS neighbor, no BTS on this node%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + /* Is there a local BTS that matches the cell_id? */ + local_neigh = gsm_bts_by_cell_id(g_net, cell_id); + if (local_neigh) { + /* But do the advertised ARFCN and BSIC match as intended? + * The user may omit ARFCN and BSIC for local cells, but if they are provided, + * they need to match. */ + if (!neighbor_ident_key_matches_bts(key, local_neigh)) { + vty_out(vty, "%% Error: bts %u: neighbor cell id %s indicates local BTS %u," + " but it does not match ARFCN+BSIC %s%s", + bts->nr, gsm0808_cell_id_name(cell_id), local_neigh->nr, + neighbor_ident_key_name(key), VTY_NEWLINE); + /* TODO: error out fatally for non-interactive VTY? */ + return CMD_WARNING; + } + return add_local_bts(vty, local_neigh); + } + + /* The cell_id is not known in this BSS, so it must be a remote cell. */ + gsm0808_cell_id_to_list(&cil, cell_id); + rc = neighbor_ident_add(g_neighbor_cells, key, &cil); + + if (rc < 0) { + const char *reason; + switch (rc) { + case -EINVAL: + reason = ": mismatching type between current and newly added cell identifier"; + break; + case -ENOSPC: + reason = ": list is full"; + break; + default: + reason = ""; + break; + } + + vty_out(vty, "%% Error adding neighbor-BSS Cell Identifier %s%s%s", + gsm0808_cell_id_name(cell_id), reason, VTY_NEWLINE); + return CMD_WARNING; + } + + vty_out(vty, "%% %s now has %d remote BSS Cell Identifier List %s%s", + neighbor_ident_key_name(key), rc, rc == 1? "entry" : "entries", VTY_NEWLINE); + return CMD_SUCCESS; +} + +static int del_by_key(struct vty *vty, const struct neighbor_ident_key *key) +{ + int removed = 0; + int rc; + struct gsm_bts *bts = vty->index; + struct gsm_bts_ref *neigh, *safe; + + if (vty->node != BTS_NODE) { + vty_out(vty, "%% Error: cannot remove BTS neighbor, not on BTS node%s", + VTY_NEWLINE); + return CMD_WARNING; + } + if (!bts) { + vty_out(vty, "%% Error: cannot remove BTS neighbor, no BTS on this node%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + /* Is there a local BTS that matches the key? */ + llist_for_each_entry_safe(neigh, safe, &bts->local_neighbors, entry) { + struct gsm_bts *neigh_bts = neigh->bts; + if (!neighbor_ident_key_matches_bts(key, neigh->bts)) + continue; + rc = gsm_bts_local_neighbor_del(bts, neigh->bts); + if (rc > 0) { + vty_out(vty, "%% Removed local neighbor bts %u to bts %u%s", + bts->nr, neigh_bts->nr, VTY_NEWLINE); + removed += rc; + } + } + + if (neighbor_ident_del(g_neighbor_cells, key)) { + vty_out(vty, "%% Removed remote BSS neighbor %s%s", + neighbor_ident_key_name(key), VTY_NEWLINE); + removed ++; + } + + if (!removed) { + vty_out(vty, "%% Cannot remove, no such neighbor: %s%s", + neighbor_ident_key_name(key), VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +DEFUN(cfg_neighbor_add_lac_arfcn_bsic, cfg_neighbor_add_lac_arfcn_bsic_cmd, + NEIGHBOR_ADD_CMD "lac <0-65535> " NEIGHBOR_IDENT_VTY_KEY_PARAMS, + NEIGHBOR_ADD_DOC "Neighbor cell by lac\n" "lac\n" NEIGHBOR_IDENT_VTY_KEY_DOC) +{ + struct neighbor_ident_key nik; + struct gsm0808_cell_id *cell_id = neighbor_ident_vty_parse_lac(vty, argv); + if (!cell_id) + return CMD_WARNING; + if (!neighbor_ident_vty_parse_key_params(vty, argv + 1, &nik)) + return CMD_WARNING; + return add_remote_or_local_bts(vty, cell_id, &nik); +} + +DEFUN(cfg_neighbor_add_lac_ci_arfcn_bsic, cfg_neighbor_add_lac_ci_arfcn_bsic_cmd, + NEIGHBOR_ADD_CMD "lac-ci <0-65535> <0-255> " NEIGHBOR_IDENT_VTY_KEY_PARAMS, + NEIGHBOR_ADD_DOC "Neighbor cell by LAC and CI\n" "LAC\n" "CI\n" NEIGHBOR_IDENT_VTY_KEY_DOC) +{ + struct neighbor_ident_key nik; + struct gsm0808_cell_id *cell_id = neighbor_ident_vty_parse_lac_ci(vty, argv); + if (!cell_id) + return CMD_WARNING; + if (!neighbor_ident_vty_parse_key_params(vty, argv + 2, &nik)) + return CMD_WARNING; + return add_remote_or_local_bts(vty, cell_id, &nik); +} + +DEFUN(cfg_neighbor_add_cgi_arfcn_bsic, cfg_neighbor_add_cgi_arfcn_bsic_cmd, + NEIGHBOR_ADD_CMD "cgi <0-999> <0-999> <0-65535> <0-255> " NEIGHBOR_IDENT_VTY_KEY_PARAMS, + NEIGHBOR_ADD_DOC "Neighbor cell by cgi\n" "MCC\n" "MNC\n" "LAC\n" "CI\n" NEIGHBOR_IDENT_VTY_KEY_DOC) +{ + struct neighbor_ident_key nik; + struct gsm0808_cell_id *cell_id = neighbor_ident_vty_parse_cgi(vty, argv); + if (!cell_id) + return CMD_WARNING; + if (!neighbor_ident_vty_parse_key_params(vty, argv + 4, &nik)) + return CMD_WARNING; + return add_remote_or_local_bts(vty, cell_id, &nik); +} + +DEFUN(cfg_neighbor_del_bts_nr, cfg_neighbor_del_bts_nr_cmd, + NEIGHBOR_DEL_CMD "bts <0-255>", + NEIGHBOR_DEL_DOC "Neighbor cell by local BTS number\n" "BTS number\n") +{ + return del_local_bts(vty, neighbor_ident_vty_parse_bts_nr(vty, argv)); +} + +DEFUN(cfg_neighbor_del_arfcn_bsic, cfg_neighbor_del_arfcn_bsic_cmd, + NEIGHBOR_DEL_CMD NEIGHBOR_IDENT_VTY_KEY_PARAMS, + NEIGHBOR_DEL_DOC NEIGHBOR_IDENT_VTY_KEY_DOC) +{ + struct neighbor_ident_key key; + + if (!neighbor_ident_vty_parse_key_params(vty, argv, &key)) + return CMD_WARNING; + + return del_by_key(vty, &key); +} + +struct write_neighbor_ident_entry_data { + struct vty *vty; + const char *indent; + struct gsm_bts *bts; +}; + +static bool write_neighbor_ident_list(const struct neighbor_ident_key *key, + const struct gsm0808_cell_id_list2 *val, + void *cb_data) +{ + struct write_neighbor_ident_entry_data *d = cb_data; + struct vty *vty = d->vty; + int i; + + if (d->bts) { + if (d->bts->nr != key->from_bts) + return true; + } else if (key->from_bts != NEIGHBOR_IDENT_KEY_ANY_BTS) + return true; + +#define NEIGH_BSS_WRITE(fmt, args...) do { \ + vty_out(vty, "%sneighbor add " fmt " arfcn %u ", d->indent, ## args, key->arfcn); \ + switch (key->bsic_kind) { \ + default: \ + case BSIC_NONE: \ + vty_out(vty, "bsic any"); \ + break; \ + case BSIC_6BIT: \ + vty_out(vty, "bsic %u", key->bsic & 0x3f); \ + break; \ + case BSIC_9BIT: \ + vty_out(vty, "bsic9 %u", key->bsic & 0x1ff); \ + break; \ + } \ + vty_out(vty, "%s", VTY_NEWLINE); \ + } while(0) + + switch (val->id_discr) { + case CELL_IDENT_LAC: + for (i = 0; i < val->id_list_len; i++) { + NEIGH_BSS_WRITE("lac %u", val->id_list[i].lac); + } + break; + case CELL_IDENT_LAC_AND_CI: + for (i = 0; i < val->id_list_len; i++) { + NEIGH_BSS_WRITE("lac-ci %u %u", + val->id_list[i].lac_and_ci.lac, + val->id_list[i].lac_and_ci.ci); + } + break; + case CELL_IDENT_WHOLE_GLOBAL: + for (i = 0; i < val->id_list_len; i++) { + const struct osmo_cell_global_id *cgi = &val->id_list[i].global; + NEIGH_BSS_WRITE("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 Identity%s", VTY_NEWLINE); + } +#undef NEIGH_BSS_WRITE + + return true; +} + +void neighbor_ident_vty_write_remote_bss(struct vty *vty, const char *indent, struct gsm_bts *bts) +{ + struct write_neighbor_ident_entry_data d = { + .vty = vty, + .indent = indent, + .bts = bts, + }; + + neighbor_ident_iter(g_neighbor_cells, write_neighbor_ident_list, &d); +} + +void neighbor_ident_vty_write_local_neighbors(struct vty *vty, const char *indent, struct gsm_bts *bts) +{ + struct gsm_bts_ref *neigh; + + llist_for_each_entry(neigh, &bts->local_neighbors, entry) { + vty_out(vty, "%sneighbor add lac-ci %u %u%s", + indent, neigh->bts->location_area_code, neigh->bts->cell_identity, + VTY_NEWLINE); + } +} + +void neighbor_ident_vty_write(struct vty *vty, const char *indent, struct gsm_bts *bts) +{ + neighbor_ident_vty_write_local_neighbors(vty, indent, bts); + neighbor_ident_vty_write_remote_bss(vty, indent, bts); +} + +DEFUN(cfg_neighbor_resolve, cfg_neighbor_resolve_cmd, + "neighbor resolve " NEIGHBOR_IDENT_VTY_KEY_PARAMS, + NEIGHBOR_DOC + "Query which cell would be the target for this neighbor ARFCN+BSIC\n" + NEIGHBOR_IDENT_VTY_KEY_DOC) +{ + int found = 0; + struct neighbor_ident_key key; + struct gsm_bts_ref *neigh; + const struct gsm0808_cell_id_list2 *res; + struct gsm_bts *bts = vty->index; + struct write_neighbor_ident_entry_data d = { + .vty = vty, + .indent = "% ", + .bts = bts, + }; + + if (vty->node != BTS_NODE) { + vty_out(vty, "%% Error: cannot query BTS neighbor, not on BTS node%s", + VTY_NEWLINE); + return CMD_WARNING; + } + if (!bts) { + vty_out(vty, "%% Error: cannot query BTS neighbor, no BTS on this node%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + if (!neighbor_ident_vty_parse_key_params(vty, argv, &key)) + return CMD_WARNING; + + /* Is there a local BTS that matches the key? */ + llist_for_each_entry(neigh, &bts->local_neighbors, entry) { + if (!neighbor_ident_key_matches_bts(&key, neigh->bts)) + continue; + vty_out(vty, "%% %s resolves to local BTS %u lac-ci %u %u%s", + neighbor_ident_key_name(&key), neigh->bts->nr, neigh->bts->location_area_code, + neigh->bts->cell_identity, VTY_NEWLINE); + found++; + } + + res = neighbor_ident_get(g_neighbor_cells, &key); + if (res) { + write_neighbor_ident_list(&key, res, &d); + found++; + } + + if (!found) + vty_out(vty, "%% No entry for %s%s", neighbor_ident_key_name(&key), VTY_NEWLINE); + + return CMD_SUCCESS; +} + +void neighbor_ident_vty_init(struct gsm_network *net, struct neighbor_ident_list *nil) +{ + g_net = net; + g_neighbor_cells = nil; + install_element(BTS_NODE, &cfg_neighbor_add_bts_nr_cmd); + install_element(BTS_NODE, &cfg_neighbor_add_lac_cmd); + install_element(BTS_NODE, &cfg_neighbor_add_lac_ci_cmd); + install_element(BTS_NODE, &cfg_neighbor_add_lac_arfcn_bsic_cmd); + install_element(BTS_NODE, &cfg_neighbor_add_lac_ci_arfcn_bsic_cmd); + install_element(BTS_NODE, &cfg_neighbor_add_cgi_arfcn_bsic_cmd); + install_element(BTS_NODE, &cfg_neighbor_del_bts_nr_cmd); + install_element(BTS_NODE, &cfg_neighbor_del_arfcn_bsic_cmd); + install_element(BTS_NODE, &cfg_neighbor_resolve_cmd); +} diff --git a/src/osmo-bsc/net_init.c b/src/osmo-bsc/net_init.c index 3ee35fe11..db3d01c1c 100644 --- a/src/osmo-bsc/net_init.c +++ b/src/osmo-bsc/net_init.c @@ -22,6 +22,7 @@ #include <osmocom/bsc/gsm_04_08_utils.h> #include <osmocom/bsc/handover_cfg.h> #include <osmocom/bsc/chan_alloc.h> +#include <osmocom/bsc/neighbor_ident.h> /* Initialize the bare minimum of struct gsm_network, minimizing required dependencies. * This part is shared among the thin programs in osmo-bsc/src/utils/. diff --git a/src/osmo-bsc/system_information.c b/src/osmo-bsc/system_information.c index d99153f24..071babab6 100644 --- a/src/osmo-bsc/system_information.c +++ b/src/osmo-bsc/system_information.c @@ -40,6 +40,9 @@ #include <osmocom/bsc/arfcn_range_encode.h> #include <osmocom/bsc/gsm_04_08_utils.h> #include <osmocom/bsc/acc_ramp.h> +#include <osmocom/bsc/neighbor_ident.h> + +struct gsm0808_cell_id_list2; /* * DCS1800 and PCS1900 have overlapping ARFCNs. We would need to set the @@ -588,6 +591,25 @@ static int bitvec2freq_list(uint8_t *chan_list, struct bitvec *bv, return bitvec2freq_list(chan_list, bv, bts, false, false); } +struct generate_bcch_chan_list__ni_iter_data { + struct gsm_bts *bts; + struct bitvec *bv; +}; + +static bool generate_bcch_chan_list__ni_iter_cb(const struct neighbor_ident_key *key, + const struct gsm0808_cell_id_list2 *val, + void *cb_data) +{ + struct generate_bcch_chan_list__ni_iter_data *data = cb_data; + + if (key->from_bts != NEIGHBOR_IDENT_KEY_ANY_BTS + && key->from_bts != data->bts->nr) + return true; + + bitvec_set_bit_pos(data->bv, key->arfcn, 1); + return true; +} + /*! generate a cell channel list as per Section 10.5.2.22 of 04.08 * \param[out] chan_list caller-provided output buffer * \param[in] bts BTS descriptor used for input data @@ -602,6 +624,7 @@ static int generate_bcch_chan_list(uint8_t *chan_list, struct gsm_bts *bts, struct bitvec *bv; int rc; + /* first we generate a bitvec of the BCCH ARFCN's in our BSC */ if (si5 && bts->neigh_list_manual_mode == NL_MODE_MANUAL_SI5SEP) bv = &bts->si_common.si5_neigh_list; else @@ -612,11 +635,29 @@ static int generate_bcch_chan_list(uint8_t *chan_list, struct gsm_bts *bts, /* Zero-initialize the bit-vector */ memset(bv->data, 0, bv->data_len); - /* first we generate a bitvec of the BCCH ARFCN's in our BSC */ - llist_for_each_entry(cur_bts, &bts->network->bts_list, list) { - if (cur_bts == bts) - continue; - bitvec_set_bit_pos(bv, cur_bts->c0->arfcn, 1); + if (llist_empty(&bts->local_neighbors)) { + /* There are no explicit neighbors, assume all BTS are. */ + llist_for_each_entry(cur_bts, &bts->network->bts_list, list) { + if (cur_bts == bts) + continue; + bitvec_set_bit_pos(bv, cur_bts->c0->arfcn, 1); + } + } else { + /* Only add explicit neighbor cells */ + struct gsm_bts_ref *neigh; + llist_for_each_entry(neigh, &bts->local_neighbors, entry) { + bitvec_set_bit_pos(bv, neigh->bts->c0->arfcn, 1); + } + } + + /* Also add neighboring BSS cells' ARFCNs */ + { + struct generate_bcch_chan_list__ni_iter_data data = { + .bv = bv, + .bts = bts, + }; + neighbor_ident_iter(bts->network->neighbor_bss_cells, + generate_bcch_chan_list__ni_iter_cb, &data); } } |