diff options
Diffstat (limited to 'src/osmo-bsc/neighbor_ident_ctrl.c')
-rw-r--r-- | src/osmo-bsc/neighbor_ident_ctrl.c | 753 |
1 files changed, 753 insertions, 0 deletions
diff --git a/src/osmo-bsc/neighbor_ident_ctrl.c b/src/osmo-bsc/neighbor_ident_ctrl.c new file mode 100644 index 000000000..a9d7b5dc5 --- /dev/null +++ b/src/osmo-bsc/neighbor_ident_ctrl.c @@ -0,0 +1,753 @@ +/* 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; +} |