diff options
author | Harald Welte <laforge@gnumonks.org> | 2011-07-13 14:52:51 +0200 |
---|---|---|
committer | Harald Welte <laforge@gnumonks.org> | 2011-07-13 14:52:51 +0200 |
commit | 07252918ea563071e6d5b7df6cc32dd7a3c8589e (patch) | |
tree | 46303280f1408eb33d21f0caae2ff99d00b9b5c2 | |
parent | 7d33bdf9626fc1edd2629d01f1b57b2e83e0e3d0 (diff) | |
parent | a86bc39cc9b37eca2ec9ddd8ea635667720967eb (diff) |
Merge branch 'daniel_ctrlif'
-rw-r--r-- | openbsc/configure.ac | 1 | ||||
-rwxr-xr-x | openbsc/contrib/bsc_control.py | 100 | ||||
-rw-r--r-- | openbsc/doc/control-interface.txt | 21 | ||||
-rw-r--r-- | openbsc/include/openbsc/bsc_nat.h | 22 | ||||
-rw-r--r-- | openbsc/include/openbsc/control_cmd.h | 151 | ||||
-rw-r--r-- | openbsc/src/Makefile.am | 2 | ||||
-rw-r--r-- | openbsc/src/libctrl/Makefile.am | 7 | ||||
-rw-r--r-- | openbsc/src/libctrl/control_cmd.c | 479 | ||||
-rw-r--r-- | openbsc/src/libctrl/control_if.c | 627 | ||||
-rw-r--r-- | openbsc/src/osmo-bsc/Makefile.am | 1 | ||||
-rw-r--r-- | openbsc/src/osmo-bsc/osmo_bsc_main.c | 168 | ||||
-rw-r--r-- | openbsc/src/osmo-bsc/osmo_bsc_msc.c | 34 | ||||
-rw-r--r-- | openbsc/src/osmo-bsc_nat/Makefile.am | 1 | ||||
-rw-r--r-- | openbsc/src/osmo-bsc_nat/bsc_nat.c | 279 | ||||
-rw-r--r-- | openbsc/src/osmo-bsc_nat/bsc_nat_utils.c | 1 | ||||
-rw-r--r-- | openbsc/src/osmo-nitb/Makefile.am | 4 | ||||
-rw-r--r-- | openbsc/src/osmo-nitb/bsc_hack.c | 3 |
17 files changed, 1899 insertions, 2 deletions
diff --git a/openbsc/configure.ac b/openbsc/configure.ac index 5ed53035b..89cfcac66 100644 --- a/openbsc/configure.ac +++ b/openbsc/configure.ac @@ -92,6 +92,7 @@ AC_OUTPUT( src/libtrau/Makefile src/libabis/Makefile src/libbsc/Makefile + src/libctrl/Makefile src/libmsc/Makefile src/libmgcp/Makefile src/libcommon/Makefile diff --git a/openbsc/contrib/bsc_control.py b/openbsc/contrib/bsc_control.py new file mode 100755 index 000000000..36c8e45cb --- /dev/null +++ b/openbsc/contrib/bsc_control.py @@ -0,0 +1,100 @@ +#!/usr/bin/python + +import sys,os +from optparse import OptionParser +import socket +import struct + +verbose = False + +def prefix_ipa_ctrl_header(data): + return struct.pack(">HBB", len(data)+1, 0xee, 0) + data + +def remove_ipa_ctrl_header(data): + if (len(data) < 4): + raise BaseException("Answer too short!") + (plen, ipa_proto, osmo_proto) = struct.unpack(">HBB", data[:4]) + if (plen + 3 > len(data)): + print "Warning: Wrong payload length (expected %i, got %i)" % (plen, len(data) - 3) + if (ipa_proto != 0xee or osmo_proto != 0): + raise BaseException("Wrong protocol in answer!") + + return data[4:plen+3], data[plen+3:] + +def connect(host, port): + if verbose: + print "Connecting to host %s:%i" % (host, port) + + sck = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sck.setblocking(1) + sck.connect((host, port)) + return sck + +def send(sck, data): + if verbose: + print "Sending \"%s\"" %(data) + data = prefix_ipa_ctrl_header(data) + sck.send(data) + +def do_set(var, value, id, sck): + setmsg = "SET %s %s %s" %(options.id, var, value) + send(sck, setmsg) + +def do_get(var, id, sck): + getmsg = "GET %s %s" %(options.id, var) + send(sck, getmsg) + +parser = OptionParser("Usage: %prog [options] var [value]") +parser.add_option("-d", "--host", dest="host", + help="connect to HOST", metavar="HOST") +parser.add_option("-p", "--port", dest="port", type="int", + help="use PORT", metavar="PORT", default=4249) +parser.add_option("-g", "--get", action="store_true", + dest="cmd_get", help="perform GET operation") +parser.add_option("-s", "--set", action="store_true", + dest="cmd_set", help="perform SET operation") +parser.add_option("-i", "--id", dest="id", default="1", + help="set id manually", metavar="ID") +parser.add_option("-v", "--verbose", action="store_true", + dest="verbose", help="be verbose", default=False) +parser.add_option("-m", "--monitor", action="store_true", + dest="monitor", help="monitor the connection for traps", default=False) + +(options, args) = parser.parse_args() + +verbose = options.verbose + +if options.cmd_set and options.cmd_get: + parser.error("Get and set options are mutually exclusive!") + +if not (options.cmd_get or options.cmd_set or options.monitor): + parser.error("One of -m, -g, or -s must be set") + +if not (options.host): + parser.error("Destination host and port required!") + +sock = connect(options.host, options.port) + +if options.cmd_set: + if len(args) < 2: + parser.error("Set requires var and value arguments") + do_set(args[0], ' '.join(args[1:]), options.id, sock) + +if options.cmd_get: + if len(args) != 1: + parser.error("Get requires the var argument") + do_get(args[0], options.id, sock) + +data = sock.recv(1024) +while (len(data)>0): + (answer, data) = remove_ipa_ctrl_header(data) + print "Got message:", answer + +if options.monitor: + while (True): + data = sock.recv(1024) + while (len(data)>0): + (answer, data) = remove_ipa_ctrl_header(data) + print "Got message:", answer + +sock.close() diff --git a/openbsc/doc/control-interface.txt b/openbsc/doc/control-interface.txt new file mode 100644 index 000000000..b43cafc35 --- /dev/null +++ b/openbsc/doc/control-interface.txt @@ -0,0 +1,21 @@ +The protocol for the control interface is wrapped inside the ip.access header +with the IPAC_PROTO_OSMO protocol ID (0xee). Inside the ip.access header is +a struct ipaccess_head_ext with protocol ID 0x00 which indicates the control +interface. + +After that the actual protocol is text based: + +* Getting the value of a variable +-> GET <id> <var> +<- GET_REPLY <id> <var> <val> +or ERROR <id> <reason> + +* Setting the value of a variable +-> SET <id> <var> <val> +<- SET_REPLY <id> <var> <val> +or ERROR <id> <reason> + +* A value changes which triggers a trap +<- TRAP <var> <val> + +<id> needs to be unique within a connection. '0' is not allowed diff --git a/openbsc/include/openbsc/bsc_nat.h b/openbsc/include/openbsc/bsc_nat.h index 97fa4fe2e..97c0a1cb4 100644 --- a/openbsc/include/openbsc/bsc_nat.h +++ b/openbsc/include/openbsc/bsc_nat.h @@ -67,6 +67,24 @@ enum { }; /* + * Pending command entry + */ +struct bsc_cmd_list { + struct llist_head list_entry; + + struct osmo_timer_list timeout; + + /* The NATed ID used on the bsc_con*/ + int nat_id; + + /* The control connection from which the command originated */ + struct ctrl_connection *ccon; + + /* The command from the control connection */ + struct ctrl_cmd *cmd; +}; + +/* * Per BSC data structure */ struct bsc_connection { @@ -94,6 +112,10 @@ struct bsc_connection { int max_endpoints; int last_endpoint; + /* track the pending commands for this BSC */ + struct llist_head cmd_pending; + int last_id; + /* a back pointer */ struct bsc_nat *nat; }; diff --git a/openbsc/include/openbsc/control_cmd.h b/openbsc/include/openbsc/control_cmd.h new file mode 100644 index 000000000..c94c7b574 --- /dev/null +++ b/openbsc/include/openbsc/control_cmd.h @@ -0,0 +1,151 @@ +#ifndef _CONTROL_CMD_H +#define _CONTROL_CMD_H + +#include <osmocom/core/msgb.h> +#include <osmocom/core/write_queue.h> + +#include <osmocom/vty/vector.h> + +#define CTRL_CMD_ERROR -1 +#define CTRL_CMD_HANDLED 0 +#define CTRL_CMD_REPLY 1 + +enum ctrl_node_type { + CTRL_NODE_ROOT, /* Root elements */ + CTRL_NODE_NET, /* Network specific (net.) */ + CTRL_NODE_BTS, /* BTS specific (net.btsN.) */ + CTRL_NODE_TRX, /* TRX specific (net.btsN.trxM.) */ + CTRL_NODE_TS, /* TS specific (net.btsN.trxM.tsI.) */ + _LAST_CTRL_NODE +}; + +enum ctrl_type { + CTRL_TYPE_UNKNOWN, + CTRL_TYPE_GET, + CTRL_TYPE_SET, + CTRL_TYPE_GET_REPLY, + CTRL_TYPE_SET_REPLY, + CTRL_TYPE_TRAP, + CTRL_TYPE_ERROR +}; + +struct ctrl_connection { + struct llist_head list_entry; + + /* The queue for sending data back */ + struct osmo_wqueue write_queue; + + /* Callback if the connection was closed */ + void (*closed_cb)(struct ctrl_connection *conn); + + /* Pending commands for this connection */ + struct llist_head cmds; +}; + +struct ctrl_cmd { + struct ctrl_connection *ccon; + enum ctrl_type type; + char *id; + void *node; + char *variable; + char *value; + char *reply; +}; + +struct ctrl_cmd_struct { + int nr_commands; + char **command; +}; + +struct ctrl_cmd_element { + const char *name; + const char *param; + struct ctrl_cmd_struct strcmd; + int (*set)(struct ctrl_cmd *cmd, void *data); + int (*get)(struct ctrl_cmd *cmd, void *data); + int (*verify)(struct ctrl_cmd *cmd, const char *value, void *data); +}; + +struct ctrl_cmd_map { + char *cmd; + enum ctrl_type type; +}; + +int ctrl_cmd_exec(vector vline, struct ctrl_cmd *command, vector node, void *data); +int ctrl_cmd_install(enum ctrl_node_type node, struct ctrl_cmd_element *cmd); +int ctrl_cmd_handle(struct ctrl_cmd *cmd, void *data); +int ctrl_cmd_send(struct osmo_wqueue *queue, struct ctrl_cmd *cmd); +struct ctrl_cmd *ctrl_cmd_parse(void *ctx, struct msgb *msg); +struct msgb *ctrl_cmd_make(struct ctrl_cmd *cmd); +struct ctrl_cmd *ctrl_cmd_cpy(void *ctx, struct ctrl_cmd *cmd); + +#define CTRL_CMD_DEFINE_RANGE(cmdname, cmdstr, dtype, element, min, max) \ +int get_##cmdname(struct ctrl_cmd *cmd, void *data) \ +{ \ + dtype *node = data; \ + cmd->reply = talloc_asprintf(cmd, "%i", node->element); \ + if (!cmd->reply) { \ + cmd->reply = "OOM"; \ + return CTRL_CMD_ERROR; \ + } \ + return CTRL_CMD_REPLY; \ +} \ +int set_##cmdname(struct ctrl_cmd *cmd, void *data) \ +{ \ + dtype *node = data; \ + int tmp = atoi(cmd->value); \ + node->element = tmp; \ + return get_##cmdname(cmd, data); \ +} \ +int verify_##cmdname(struct ctrl_cmd *cmd, const char *value, void *data) \ +{ \ + int tmp = atoi(value); \ + if ((tmp >= min)&&(tmp <= max)) { \ + return 0; \ + } \ + return -1; \ +} \ +struct ctrl_cmd_element cmd_##cmdname = { \ + .name = cmdstr, \ + .param = NULL, \ + .get = &get_##cmdname, \ + .set = &set_##cmdname, \ + .verify = &verify_##cmdname, \ +} + +#define CTRL_CMD_DEFINE_STRING(cmdname, cmdstr, dtype, element) \ +int get_##cmdname(struct ctrl_cmd *cmd, dtype *data) \ +{ \ + cmd->reply = talloc_asprintf(cmd, "%s", data->element); \ + if (!cmd->reply) { \ + cmd->reply = "OOM"; \ + return CTRL_CMD_ERROR; \ + } \ + return CTRL_CMD_REPLY; \ +} \ +int set_##cmdname(struct ctrl_cmd *cmd, dtype *data) \ +{ \ + bsc_replace_string(cmd->node, &data->element, cmd->value); \ + return get_##cmdname(cmd, data); \ +} \ +struct ctrl_cmd_element cmd_##cmdname = { \ + .name = cmdstr, \ + .param = NULL, \ + .get = &get_##cmdname, \ + .set = &set_##cmdname, \ + .verify = NULL, \ +} + +#define CTRL_CMD_DEFINE(cmdname, cmdstr) \ +int get_##cmdname(struct ctrl_cmd *cmd, void *data); \ +int set_##cmdname(struct ctrl_cmd *cmd, void *data); \ +int verify_##cmdname(struct ctrl_cmd *cmd, const char *value, void *data); \ +struct ctrl_cmd_element cmd_##cmdname = { \ + .name = cmdstr, \ + .param = NULL, \ + .get = &get_##cmdname, \ + .set = &set_##cmdname, \ + .verify = &verify_##cmdname, \ +} + +#endif /* _CONTROL_CMD_H */ diff --git a/openbsc/src/Makefile.am b/openbsc/src/Makefile.am index df7b936e7..c7de811f3 100644 --- a/openbsc/src/Makefile.am +++ b/openbsc/src/Makefile.am @@ -2,7 +2,7 @@ INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir) AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS) AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(COVERAGE_LDFLAGS) -SUBDIRS = libcommon libabis libmgcp libbsc libmsc libtrau osmo-nitb osmo-bsc_mgcp utils ipaccess libgb gprs +SUBDIRS = libcommon libabis libmgcp libbsc libmsc libtrau libctrl osmo-nitb osmo-bsc_mgcp utils ipaccess libgb gprs # Conditional modules if BUILD_NAT diff --git a/openbsc/src/libctrl/Makefile.am b/openbsc/src/libctrl/Makefile.am new file mode 100644 index 000000000..286929b89 --- /dev/null +++ b/openbsc/src/libctrl/Makefile.am @@ -0,0 +1,7 @@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir) +AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS) +AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS) + +noinst_LIBRARIES = libctrl.a + +libctrl_a_SOURCES = control_if.c control_cmd.c diff --git a/openbsc/src/libctrl/control_cmd.c b/openbsc/src/libctrl/control_cmd.c new file mode 100644 index 000000000..b5cff6859 --- /dev/null +++ b/openbsc/src/libctrl/control_cmd.c @@ -0,0 +1,479 @@ +/* SNMP-like status interface + * + * (C) 2010-2011 by Daniel Willmann <daniel@totalueberwachung.de> + * (C) 2010-2011 by On-Waves + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <ctype.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include <openbsc/control_cmd.h> +#include <openbsc/debug.h> +#include <openbsc/vty.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/talloc.h> + +#include <osmocom/vty/command.h> +#include <osmocom/vty/vector.h> + +extern vector ctrl_node_vec; + +static struct ctrl_cmd_map ccm[] = { + {"GET", CTRL_TYPE_GET}, + {"SET", CTRL_TYPE_SET}, + {"GET_REPLY", CTRL_TYPE_GET_REPLY}, + {"SET_REPLY", CTRL_TYPE_SET_REPLY}, + {"TRAP", CTRL_TYPE_TRAP}, + {"ERROR", CTRL_TYPE_ERROR}, + {NULL} +}; + +int ctrl_cmd_str2type(char *s) +{ + int i; + for (i=0; ccm[i].cmd != NULL; i++) { + if (strcasecmp(s, ccm[i].cmd) == 0) + return ccm[i].type; + } + return CTRL_TYPE_UNKNOWN; +} + +char *ctrl_cmd_type2str(int type) +{ + int i; + for (i=0; ccm[i].cmd != NULL; i++) { + if (ccm[i].type == type) + return ccm[i].cmd; + } + return NULL; +} + +/* Functions from libosmocom */ +extern vector cmd_make_descvec(const char *string, const char *descstr); + +/* Get the ctrl_cmd_element that matches this command */ +static struct ctrl_cmd_element *ctrl_cmd_get_element_match(vector vline, vector node) +{ + int index, j; + const char *desc; + struct ctrl_cmd_element *cmd_el; + struct ctrl_cmd_struct *cmd_desc; + char *str; + + for (index = 0; index < vector_active(node); index++) { + if ((cmd_el = vector_slot(node, index))) { + cmd_desc = &cmd_el->strcmd; + if (cmd_desc->nr_commands > vector_active(vline)) + continue; + for (j =0; j < vector_active(vline); j++) { + str = vector_slot(vline, j); + desc = cmd_desc->command[j]; + if (desc[0] == '*') + return cmd_el; /* Partial match */ + if (strcmp(desc, str) != 0) + break; + } + /* We went through all the elements and all matched */ + if (j == cmd_desc->nr_commands) + return cmd_el; + } + } + + return NULL; +} + +int ctrl_cmd_exec(vector vline, struct ctrl_cmd *command, vector node, void *data) +{ + int ret = CTRL_CMD_ERROR; + struct ctrl_cmd_element *cmd_el; + + if ((command->type != CTRL_TYPE_GET) && (command->type != CTRL_TYPE_SET)) { + command->reply = "Trying to execute something not GET or SET"; + goto out; + } + if ((command->type == CTRL_TYPE_SET) && (!command->value)) { + command->reply = "SET without a value"; + goto out; + } + + if (!vline) + goto out; + + cmd_el = ctrl_cmd_get_element_match(vline, node); + + if (!cmd_el) { + command->reply = "Command not found"; + goto out; + } + + if (command->type == CTRL_TYPE_SET) { + if (!cmd_el->set) { + command->reply = "SET not implemented"; + goto out; + } + if (cmd_el->verify) { + if ((ret = cmd_el->verify(command, command->value, data))) { + ret = CTRL_CMD_ERROR; + command->reply = "Value failed verification."; + goto out; + } + } else if (cmd_el->param) { + LOGP(DINP, LOGL_NOTICE, "Parameter verification unimplemented, continuing without\n"); + } + ret = cmd_el->set(command, data); + goto out; + } else if (command->type == CTRL_TYPE_GET) { + if (!cmd_el->get) { + command->reply = "GET not implemented"; + goto out; + } + ret = cmd_el->get(command, data); + goto out; + } +out: + if (ret == CTRL_CMD_REPLY) { + if (command->type == CTRL_TYPE_SET) { + command->type = CTRL_TYPE_SET_REPLY; + } else if (command->type == CTRL_TYPE_GET) { + command->type = CTRL_TYPE_GET_REPLY; + } + } else if (ret == CTRL_CMD_ERROR) { + command->type = CTRL_TYPE_ERROR; + } + return ret; +} + +static void add_word(struct ctrl_cmd_struct *cmd, + const char *start, const char *end) +{ + if (!cmd->command) { + cmd->command = talloc_zero_array(tall_vty_vec_ctx, + char*, 1); + cmd->nr_commands = 0; + } else { + cmd->command = talloc_realloc(tall_vty_vec_ctx, + cmd->command, char*, + cmd->nr_commands + 1); + } + + cmd->command[cmd->nr_commands++] = talloc_strndup(cmd->command, + start, end - start); +} + +static void create_cmd_struct(struct ctrl_cmd_struct *cmd, const char *name) +{ + const char *cur, *word; + + for (cur = name, word = NULL; cur[0] != '\0'; ++cur) { + /* warn about optionals */ + if (cur[0] == '(' || cur[0] == ')' || cur[0] == '|') { + LOGP(DINP, LOGL_ERROR, + "Optionals are not supported in '%s'\n", name); + goto failure; + } + + if (isspace(cur[0])) { + if (word) { + add_word(cmd, word, cur); + word = NULL; + } + continue; + } + + if (!word) + word = cur; + } + + if (word) + add_word(cmd, word, cur); + + return; +failure: + cmd->nr_commands = 0; + talloc_free(cmd->command); +} + +int ctrl_cmd_install(enum ctrl_node_type node, struct ctrl_cmd_element *cmd) +{ + vector cmds_vec; + + cmds_vec = vector_lookup_ensure(ctrl_node_vec, node); + + if (!cmds_vec) { + cmds_vec = vector_init(5); + if (!cmds_vec) { + LOGP(DINP, LOGL_ERROR, "vector_init failed.\n"); + return -ENOMEM; + } + vector_set_index(ctrl_node_vec, node, cmds_vec); + } + + vector_set(cmds_vec, cmd); + + create_cmd_struct(&cmd->strcmd, cmd->name); + return 0; +} + +struct ctrl_cmd *ctrl_cmd_cpy(void *ctx, struct ctrl_cmd *cmd) +{ + struct ctrl_cmd *cmd2; + + cmd2 = talloc_zero(ctx, struct ctrl_cmd); + if (!cmd2) + return NULL; + + cmd2->type = cmd->type; + if (cmd->id) { + cmd2->id = talloc_strdup(cmd2, cmd->id); + if (!cmd2->id) + goto err; + } + if (cmd->variable) { + cmd2->variable = talloc_strdup(cmd2, cmd->variable); + if (!cmd2->variable) + goto err; + } + if (cmd->value) { + cmd2->value = talloc_strdup(cmd2, cmd->value); + if (!cmd2->value) + goto err; + } + if (cmd->reply) { + cmd2->reply = talloc_strdup(cmd2, cmd->reply); + if (!cmd2->reply) + goto err; + } + + return cmd2; +err: + talloc_free(cmd2); + return NULL; +} + +struct ctrl_cmd *ctrl_cmd_parse(void *ctx, struct msgb *msg) +{ + char *str, *tmp, *saveptr = NULL; + char *var, *val; + struct ctrl_cmd *cmd; + + cmd = talloc_zero(ctx, struct ctrl_cmd); + if (!cmd) { + LOGP(DINP, LOGL_ERROR, "Failed to allocate.\n"); + return NULL; + } + + /* Make sure input is NULL terminated */ + msgb_put_u8(msg, 0); + str = (char *) msg->l2h; + + tmp = strtok_r(str, " ", &saveptr); + if (!tmp) { + cmd->type = CTRL_TYPE_ERROR; + cmd->id = "err"; + cmd->reply = "Request malformed"; + goto err; + } + + cmd->type = ctrl_cmd_str2type(tmp); + if (cmd->type == CTRL_TYPE_UNKNOWN) { + cmd->type = CTRL_TYPE_ERROR; + cmd->id = "err"; + cmd->reply = "Request type unknown"; + goto err; + } + + tmp = strtok_r(NULL, " ", &saveptr); + + if (!tmp) { + cmd->type = CTRL_TYPE_ERROR; + cmd->id = "err"; + cmd->reply = "Missing ID"; + goto err; + } + cmd->id = talloc_strdup(cmd, tmp); + if (!cmd->id) + goto oom; + + switch (cmd->type) { + case CTRL_TYPE_GET: + var = strtok_r(NULL, " ", &saveptr); + if (!var) { + cmd->type = CTRL_TYPE_ERROR; + cmd->reply = "GET incomplete"; + LOGP(DINP, LOGL_NOTICE, "GET Command incomplete\n"); + goto err; + } + cmd->variable = talloc_strdup(cmd, var); + LOGP(DINP, LOGL_DEBUG, "Command: GET %s\n", cmd->variable); + break; + case CTRL_TYPE_SET: + var = strtok_r(NULL, " ", &saveptr); + val = strtok_r(NULL, " ", &saveptr); + if (!var || !val) { + cmd->type = CTRL_TYPE_ERROR; + cmd->reply = "SET incomplete"; + LOGP(DINP, LOGL_NOTICE, "SET Command incomplete\n"); + goto err; + } + cmd->variable = talloc_strdup(cmd, var); + cmd->value = talloc_strdup(cmd, val); + if (!cmd->variable || !cmd->value) + goto oom; + LOGP(DINP, LOGL_DEBUG, "Command: SET %s = %s\n", cmd->variable, cmd->value); + break; + case CTRL_TYPE_GET_REPLY: + case CTRL_TYPE_SET_REPLY: + case CTRL_TYPE_TRAP: + var = strtok_r(NULL, " ", &saveptr); + val = strtok_r(NULL, " ", &saveptr); + if (!var || !val) { + cmd->type = CTRL_TYPE_ERROR; + cmd->reply = "Trap/Reply incomplete"; + LOGP(DINP, LOGL_NOTICE, "Trap/Reply incomplete\n"); + goto err; + } + cmd->variable = talloc_strdup(cmd, var); + cmd->reply = talloc_strdup(cmd, val); + if (!cmd->variable || !cmd->reply) + goto oom; + LOGP(DINP, LOGL_DEBUG, "Command: TRAP/REPLY %s: %s\n", cmd->variable, cmd->reply); + break; + case CTRL_TYPE_ERROR: + var = strtok_r(NULL, "\0", &saveptr); + if (!var) { + cmd->reply = ""; + goto err; + } + cmd->reply = talloc_strdup(cmd, var); + if (!cmd->reply) + goto oom; + LOGP(DINP, LOGL_DEBUG, "Command: ERROR %s\n", cmd->reply); + break; + case CTRL_TYPE_UNKNOWN: + default: + cmd->type = CTRL_TYPE_ERROR; + cmd->reply = "Unknown type"; + goto err; + } + + return cmd; +oom: + cmd->type = CTRL_TYPE_ERROR; + cmd->id = "err"; + cmd->reply = "OOM"; +err: + talloc_free(cmd); + return NULL; +} + +struct msgb *ctrl_cmd_make(struct ctrl_cmd *cmd) +{ + struct msgb *msg; + char *type, *tmp; + + if (!cmd->id) + return NULL; + + msg = msgb_alloc_headroom(4096, 128, "ctrl command make"); + if (!msg) + return NULL; + + type = ctrl_cmd_type2str(cmd->type); + + switch (cmd->type) { + case CTRL_TYPE_GET: + if (!cmd->variable) + goto err; + + tmp = talloc_asprintf(cmd, "%s %s %s", type, cmd->id, cmd->variable); + if (!tmp) { + LOGP(DINP, LOGL_ERROR, "Failed to allocate cmd.\n"); + goto err; + } + + msg->l2h = msgb_put(msg, strlen(tmp)); + memcpy(msg->l2h, tmp, strlen(tmp)); + talloc_free(tmp); + break; + case CTRL_TYPE_SET: + if (!cmd->variable || !cmd->value) + goto err; + + tmp = talloc_asprintf(cmd, "%s %s %s %s", type, cmd->id, cmd->variable, + cmd->value); + if (!tmp) { + LOGP(DINP, LOGL_ERROR, "Failed to allocate cmd.\n"); + goto err; + } + + msg->l2h = msgb_put(msg, strlen(tmp)); + memcpy(msg->l2h, tmp, strlen(tmp)); + talloc_free(tmp); + break; + case CTRL_TYPE_GET_REPLY: + case CTRL_TYPE_SET_REPLY: + case CTRL_TYPE_TRAP: + if (!cmd->variable || !cmd->reply) + goto err; + + tmp = talloc_asprintf(cmd, "%s %s %s %s", type, cmd->id, cmd->variable, + cmd->reply); + if (!tmp) { + LOGP(DINP, LOGL_ERROR, "Failed to allocate cmd.\n"); + goto err; + } + + msg->l2h = msgb_put(msg, strlen(tmp)); + memcpy(msg->l2h, tmp, strlen(tmp)); + talloc_free(tmp); + break; + case CTRL_TYPE_ERROR: + if (!cmd->reply) + goto err; + + tmp = talloc_asprintf(cmd, "%s %s %s", type, cmd->id, + cmd->reply); + if (!tmp) { + LOGP(DINP, LOGL_ERROR, "Failed to allocate cmd.\n"); + goto err; + } + + msg->l2h = msgb_put(msg, strlen(tmp)); + memcpy(msg->l2h, tmp, strlen(tmp)); + talloc_free(tmp); + break; + default: + LOGP(DINP, LOGL_NOTICE, "Unknown command type %i\n", cmd->type); + goto err; + break; + } + + return msg; + +err: + msgb_free(msg); + return NULL; +} diff --git a/openbsc/src/libctrl/control_if.c b/openbsc/src/libctrl/control_if.c new file mode 100644 index 000000000..0e625cd79 --- /dev/null +++ b/openbsc/src/libctrl/control_if.c @@ -0,0 +1,627 @@ +/* SNMP-like status interface + * + * (C) 2010-2011 by Daniel Willmann <daniel@totalueberwachung.de> + * (C) 2010-2011 by On-Waves + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include <arpa/inet.h> + +#include <netinet/tcp.h> + +#include <sys/fcntl.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/types.h> + +#include <openbsc/control_cmd.h> +#include <openbsc/debug.h> +#include <openbsc/e1_input.h> +#include <openbsc/gsm_data.h> +#include <openbsc/ipaccess.h> +#include <openbsc/socket.h> +#include <openbsc/subchan_demux.h> + +#include <openbsc/abis_rsl.h> +#include <openbsc/abis_nm.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/rate_ctr.h> +#include <osmocom/core/select.h> +#include <osmocom/core/statistics.h> +#include <osmocom/core/talloc.h> + +#include <osmocom/gsm/tlv.h> + +#include <osmocom/vty/command.h> +#include <osmocom/vty/vector.h> + +struct ctrl_handle { + struct osmo_fd listen_fd; + struct gsm_network *gsmnet; +}; + +vector ctrl_node_vec; + +int ctrl_cmd_send(struct osmo_wqueue *queue, struct ctrl_cmd *cmd) +{ + int ret; + struct msgb *msg; + + msg = ctrl_cmd_make(cmd); + if (!msg) { + LOGP(DINP, LOGL_ERROR, "Could not generate msg\n"); + return -1; + } + + ipaccess_prepend_header_ext(msg, IPAC_PROTO_EXT_CTRL); + ipaccess_prepend_header(msg, IPAC_PROTO_OSMO); + + ret = osmo_wqueue_enqueue(queue, msg); + if (ret != 0) { + LOGP(DINP, LOGL_ERROR, "Failed to enqueue the command.\n"); + msgb_free(msg); + } + return ret; +} + +int ctrl_cmd_handle(struct ctrl_cmd *cmd, void *data) +{ + char *token, *request; + int num, i, j, ret, node; + struct gsm_network *gsmnet = data; + + struct gsm_network *net = NULL; + struct gsm_bts *bts = NULL; + struct gsm_bts_trx *trx = NULL; + struct gsm_bts_trx_ts *ts = NULL; + vector vline, cmdvec, cmds_vec; + + ret = CTRL_CMD_ERROR; + cmd->reply = "Someone forgot to fill in the reply."; + cmd->node = NULL; + node = CTRL_NODE_ROOT; + + request = talloc_strdup(tall_bsc_ctx, cmd->variable); + if (!request) + goto err; + + for (i=0;i<strlen(request);i++) { + if (request[i] == '.') + request[i] = ' '; + } + + vline = cmd_make_strvec(request); + talloc_free(request); + if (!vline) + goto err; + + for (i=0;i<vector_active(vline);i++) { + token = vector_slot(vline, i); + /* TODO: We need to make sure that the following chars are digits + * and/or use strtol to check if number conversion was successful + * Right now something like net.bts_stats will not work */ + if (!strcmp(token, "net")) { + net = gsmnet; + if (!net) + break; + cmd->node = net; + node = CTRL_NODE_NET; + } else if (!strncmp(token, "bts", 3)) { + if (!net) + break; + num = atoi(&token[3]); + bts = gsm_bts_num(net, num); + if (!bts) + break; + cmd->node = bts; + node = CTRL_NODE_BTS; + } else if (!strncmp(token, "trx", 3)) { + if (!bts) + break; + num = atoi(&token[3]); + trx = gsm_bts_trx_num(bts, num); + if (!trx) + break; + cmd->node = trx; + node = CTRL_NODE_TRX; + } else if (!strncmp(token, "ts", 2)) { + if (!trx) + break; + num = atoi(&token[2]); + if ((num >= 0) && (num < TRX_NR_TS)) + ts = &trx->ts[num]; + if (!ts) + break; + cmd->node = ts; + node = CTRL_NODE_TS; + } else { + /* If we're here the rest must be the command */ + cmdvec = vector_init(vector_active(vline)-i); + for (j=i; j<vector_active(vline); j++) { + vector_set(cmdvec, vector_slot(vline, j)); + } + + /* Get the command vector of the right node */ + cmds_vec = vector_lookup(ctrl_node_vec, node); + + if (!cmds_vec) { + cmd->reply = "Command not found"; + vector_free(cmdvec); + break; + } + + ret = ctrl_cmd_exec(cmdvec, cmd, cmds_vec, data); + + vector_free(cmdvec); + break; + } + } + + cmd_free_strvec(vline); + +err: + if (ret == CTRL_CMD_ERROR) + cmd->type = CTRL_TYPE_ERROR; + return ret; +} + +static void control_close_conn(struct ctrl_connection *ccon) +{ + close(ccon->write_queue.bfd.fd); + osmo_fd_unregister(&ccon->write_queue.bfd); + if (ccon->closed_cb) + ccon->closed_cb(ccon); + talloc_free(ccon); +} + +static int handle_control_read(struct osmo_fd * bfd) +{ + int ret = -1, error; + struct osmo_wqueue *queue; + struct ctrl_connection *ccon; + struct ipaccess_head *iph; + struct ipaccess_head_ext *iph_ext; + struct msgb *msg; + struct ctrl_cmd *cmd; + struct ctrl_handle *ctrl = bfd->data; + + queue = container_of(bfd, struct osmo_wqueue, bfd); + ccon = container_of(queue, struct ctrl_connection, write_queue); + + msg = ipaccess_read_msg(bfd, &error); + + if (!msg) { + if (error == 0) + LOGP(DINP, LOGL_INFO, "The control connection was closed\n"); + else + LOGP(DINP, LOGL_ERROR, "Failed to parse ip access message: %d\n", error); + + goto err; + } + + if (msg->len < sizeof(*iph) + sizeof(*iph_ext)) { + LOGP(DINP, LOGL_ERROR, "The message is too short.\n"); + goto err; + } + + iph = (struct ipaccess_head *) msg->data; + if (iph->proto != IPAC_PROTO_OSMO) { + LOGP(DINP, LOGL_ERROR, "Protocol mismatch. We got 0x%x\n", iph->proto); + goto err; + } + + iph_ext = (struct ipaccess_head_ext *) iph->data; + if (iph_ext->proto != IPAC_PROTO_EXT_CTRL) { + LOGP(DINP, LOGL_ERROR, "Extended protocol mismatch. We got 0x%x\n", iph_ext->proto); + goto err; + } + + msg->l2h = iph_ext->data; + + cmd = ctrl_cmd_parse(ccon, msg); + + if (cmd) { + cmd->ccon = ccon; + if (ctrl_cmd_handle(cmd, ctrl->gsmnet) != CTRL_CMD_HANDLED) { + ctrl_cmd_send(queue, cmd); + talloc_free(cmd); + } + } else { + cmd = talloc_zero(ccon, struct ctrl_cmd); + if (!cmd) + goto err; + LOGP(DINP, LOGL_ERROR, "Command parser error.\n"); + cmd->type = CTRL_TYPE_ERROR; + cmd->id = "err"; + cmd->reply = "Command parser error."; + ctrl_cmd_send(queue, cmd); + talloc_free(cmd); + } + + msgb_free(msg); + return 0; + +err: + control_close_conn(ccon); + msgb_free(msg); + return ret; +} + +static int control_write_cb(struct osmo_fd *bfd, struct msgb *msg) +{ + int rc; + + rc = write(bfd->fd, msg->data, msg->len); + if (rc != msg->len) + LOGP(DINP, LOGL_ERROR, "Failed to write message to the control connection.\n"); + + return rc; +} + +static struct ctrl_connection *ctrl_connection_alloc(void *ctx) +{ + struct ctrl_connection *ccon = talloc_zero(ctx, struct ctrl_connection); + if (!ccon) + return NULL; + + osmo_wqueue_init(&ccon->write_queue, 100); + /* Error handling here? */ + + INIT_LLIST_HEAD(&ccon->cmds); + return ccon; +} + +static int listen_fd_cb(struct osmo_fd *listen_bfd, unsigned int what) +{ + int ret, fd, on; + struct ctrl_connection *ccon; + struct sockaddr_in sa; + socklen_t sa_len = sizeof(sa); + + + if (!(what & BSC_FD_READ)) + return 0; + + fd = accept(listen_bfd->fd, (struct sockaddr *) &sa, &sa_len); + if (fd < 0) { + perror("accept"); + return fd; + } + LOGP(DINP, LOGL_INFO, "accept()ed new control connection from %s\n", + inet_ntoa(sa.sin_addr)); + + on = 1; + ret = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)); + if (ret != 0) { + LOGP(DNAT, LOGL_ERROR, "Failed to set TCP_NODELAY: %s\n", strerror(errno)); + close(fd); + return ret; + } + ccon = ctrl_connection_alloc(listen_bfd->data); + if (!ccon) { + LOGP(DINP, LOGL_ERROR, "Failed to allocate.\n"); + close(fd); + return -1; + } + + ccon->write_queue.bfd.data = listen_bfd->data; + ccon->write_queue.bfd.fd = fd; + ccon->write_queue.bfd.when = BSC_FD_READ; + ccon->write_queue.read_cb = handle_control_read; + ccon->write_queue.write_cb = control_write_cb; + + ret = osmo_fd_register(&ccon->write_queue.bfd); + if (ret < 0) { + LOGP(DINP, LOGL_ERROR, "Could not register FD.\n"); + close(ccon->write_queue.bfd.fd); + talloc_free(ccon); + } + + return ret; +} + +static uint64_t get_rate_ctr_value(const struct rate_ctr *ctr, int intv) +{ + if (intv >= RATE_CTR_INTV_NUM) + return 0; + + /* Absolute value */ + if (intv == -1) { + return ctr->current; + } else { + return ctr->intv[intv].rate; + } +} + +static char *get_all_rate_ctr_in_group(const struct rate_ctr_group *ctrg, int intv) +{ + int i; + char *counters = talloc_strdup(tall_bsc_ctx, ""); + if (!counters) + return NULL; + + for (i=0;i<ctrg->desc->num_ctr;i++) { + counters = talloc_asprintf_append(counters, "\n%s.%u.%s %lu", + ctrg->desc->group_name_prefix, ctrg->idx, + ctrg->desc->ctr_desc[i].name, + get_rate_ctr_value(&ctrg->ctr[i], intv)); + if (!counters) + return NULL; + } + return counters; +} + +static int get_rate_ctr_group(const char *ctr_group, int intv, struct ctrl_cmd *cmd) +{ + int i; + char *counters; + struct rate_ctr_group *ctrg; + + cmd->reply = talloc_asprintf(cmd, "All counters in group %s", ctr_group); + if (!cmd->reply) + goto oom; + + for (i=0;;i++) { + ctrg = rate_ctr_get_group_by_name_idx(ctr_group, i); + if (!ctrg) + break; + + counters = get_all_rate_ctr_in_group(ctrg, intv); + if (!counters) + goto oom; + + cmd->reply = talloc_asprintf_append(cmd->reply, "%s", counters); + talloc_free(counters); + if (!cmd->reply) + goto oom; + } + + /* We found no counter group by that name */ + if (i == 0) { + cmd->reply = talloc_asprintf(cmd, "No counter group with name %s.", ctr_group); + return CTRL_CMD_ERROR; + } + + return CTRL_CMD_REPLY; +oom: + cmd->reply = "OOM."; + return CTRL_CMD_ERROR; +} + +static int get_rate_ctr_group_idx(const struct rate_ctr_group *ctrg, int intv, struct ctrl_cmd *cmd) +{ + char *counters; + + counters = get_all_rate_ctr_in_group(ctrg, intv); + if (!counters) + goto oom; + + cmd->reply = talloc_asprintf(cmd, "All counters in %s.%u%s", + ctrg->desc->group_name_prefix, ctrg->idx, counters); + talloc_free(counters); + if (!cmd->reply) + goto oom; + + return CTRL_CMD_REPLY; +oom: + cmd->reply = "OOM."; + return CTRL_CMD_ERROR; +} + +/* rate_ctr */ +CTRL_CMD_DEFINE(rate_ctr, "rate_ctr *"); +int get_rate_ctr(struct ctrl_cmd *cmd, void *data) +{ + int intv; + unsigned int idx; + char *ctr_group, *ctr_idx, *ctr_name, *tmp, *dup, *saveptr, *interval; + struct rate_ctr_group *ctrg; + const struct rate_ctr *ctr; + + dup = talloc_strdup(cmd, cmd->variable); + if (!dup) + goto oom; + + /* Skip over possible prefixes (net.) */ + tmp = strstr(dup, "rate_ctr"); + if (!tmp) { + talloc_free(dup); + cmd->reply = "rate_ctr not a token in rate_ctr command!"; + goto err; + } + + strtok_r(tmp, ".", &saveptr); + interval = strtok_r(NULL, ".", &saveptr); + if (!interval) { + talloc_free(dup); + cmd->reply = "Missing interval."; + goto err; + } + + if (!strcmp(interval, "abs")) { + intv = -1; + } else if (!strcmp(interval, "per_sec")) { + intv = RATE_CTR_INTV_SEC; + } else if (!strcmp(interval, "per_min")) { + intv = RATE_CTR_INTV_MIN; + } else if (!strcmp(interval, "per_hour")) { + intv = RATE_CTR_INTV_HOUR; + } else if (!strcmp(interval, "per_day")) { + intv = RATE_CTR_INTV_DAY; + } else { + talloc_free(dup); + cmd->reply = "Wrong interval."; + goto err; + } + + ctr_group = strtok_r(NULL, ".", &saveptr); + tmp = strtok_r(NULL, ".", &saveptr); + if (!ctr_group || !tmp) { + talloc_free(dup); + cmd->reply = "Counter group must be of form a.b"; + goto err; + } + ctr_group[strlen(ctr_group)] = '.'; + + ctr_idx = strtok_r(NULL, ".", &saveptr); + if (!ctr_idx) { + talloc_free(dup); + return get_rate_ctr_group(ctr_group, intv, cmd); + } + idx = atoi(ctr_idx); + + ctrg = rate_ctr_get_group_by_name_idx(ctr_group, idx); + if (!ctrg) { + talloc_free(dup); + cmd->reply = "Counter group not found."; + goto err; + } + + ctr_name = strtok_r(NULL, "\0", &saveptr); + if (!ctr_name) { + talloc_free(dup); + return get_rate_ctr_group_idx(ctrg, intv, cmd); + } + + ctr = rate_ctr_get_by_name(ctrg, ctr_name); + if (!ctr) { + cmd->reply = "Counter name not found."; + talloc_free(dup); + goto err; + } + + talloc_free(dup); + + cmd->reply = talloc_asprintf(cmd, "%lu", get_rate_ctr_value(ctr, intv)); + if (!cmd->reply) + goto oom; + + return CTRL_CMD_REPLY; +oom: + cmd->reply = "OOM"; +err: + return CTRL_CMD_ERROR; +} + +int set_rate_ctr(struct ctrl_cmd *cmd, void *data) +{ + cmd->reply = "Can't set rate counter."; + + return CTRL_CMD_ERROR; +} + +int verify_rate_ctr(struct ctrl_cmd *cmd, const char *value, void *data) +{ + return 0; +} + +/* counter */ +CTRL_CMD_DEFINE(counter, "counter *"); +int get_counter(struct ctrl_cmd *cmd, void *data) +{ + char *ctr_name, *tmp, *dup, *saveptr; + struct osmo_counter *counter; + + cmd->reply = "OOM"; + dup = talloc_strdup(cmd, cmd->variable); + if (!dup) + goto err; + + + tmp = strstr(dup, "counter"); + if (!tmp) { + talloc_free(dup); + goto err; + } + + strtok_r(tmp, ".", &saveptr); + ctr_name = strtok_r(NULL, "\0", &saveptr); + + if (!ctr_name) + goto err; + + counter = osmo_counter_get_by_name(ctr_name); + if (!counter) { + cmd->reply = "Counter name not found."; + talloc_free(dup); + goto err; + } + + talloc_free(dup); + + cmd->reply = talloc_asprintf(cmd, "%lu", counter->value); + if (!cmd->reply) { + cmd->reply = "OOM"; + goto err; + } + + return CTRL_CMD_REPLY; +err: + return CTRL_CMD_ERROR; +} + +int set_counter(struct ctrl_cmd *cmd, void *data) +{ + + cmd->reply = "Can't set counter."; + + return CTRL_CMD_ERROR; +} + +int verify_counter(struct ctrl_cmd *cmd, const char *value, void *data) +{ + return 0; +} + +int controlif_setup(struct gsm_network *gsmnet, uint16_t port) +{ + int ret; + struct ctrl_handle *ctrl; + + ctrl = talloc_zero(tall_bsc_ctx, struct ctrl_handle); + if (!ctrl) + return -ENOMEM; + + ctrl->gsmnet = gsmnet; + + ctrl_node_vec = vector_init(5); + if (!ctrl_node_vec) + return -ENOMEM; + + /* Listen for control connections */ + ret = make_sock(&ctrl->listen_fd, IPPROTO_TCP, 0, port, + 0, listen_fd_cb, ctrl); + if (ret < 0) { + talloc_free(ctrl); + return ret; + } + + ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_rate_ctr); + ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_counter); + + return ret; +} diff --git a/openbsc/src/osmo-bsc/Makefile.am b/openbsc/src/osmo-bsc/Makefile.am index 9a7619ba2..f32705c01 100644 --- a/openbsc/src/osmo-bsc/Makefile.am +++ b/openbsc/src/osmo-bsc/Makefile.am @@ -15,4 +15,5 @@ osmo_bsc_LDADD = $(top_builddir)/src/libbsc/libbsc.a \ $(top_builddir)/src/libabis/libabis.a \ $(top_builddir)/src/libtrau/libtrau.a \ $(top_builddir)/src/libcommon/libcommon.a \ + $(top_builddir)/src/libctrl/libctrl.a \ $(LIBOSMOSCCP_LIBS) diff --git a/openbsc/src/osmo-bsc/osmo_bsc_main.c b/openbsc/src/osmo-bsc/osmo_bsc_main.c index 42c74cc60..248944b6c 100644 --- a/openbsc/src/osmo-bsc/osmo_bsc_main.c +++ b/openbsc/src/osmo-bsc/osmo_bsc_main.c @@ -18,6 +18,7 @@ * */ +#include <openbsc/control_cmd.h> #include <openbsc/debug.h> #include <openbsc/gsm_data.h> #include <openbsc/osmo_bsc.h> @@ -25,10 +26,12 @@ #include <openbsc/osmo_msc_data.h> #include <openbsc/signal.h> #include <openbsc/vty.h> +#include <openbsc/ipaccess.h> #include <osmocom/core/application.h> #include <osmocom/core/talloc.h> #include <osmocom/core/process.h> +#include <osmocom/gsm/protocol/gsm_12_21.h> #include <osmocom/sccp/sccp.h> @@ -50,6 +53,8 @@ static const char *rf_ctl = NULL; extern const char *openbsc_copyright; static int daemonize = 0; +extern void controlif_setup(struct gsm_network *gsmnet, uint16_t port); + static void print_usage() { printf("Usage: osmo-bsc\n"); @@ -168,6 +173,164 @@ static void signal_handler(int signal) } } +struct location { + double lat; + double lon; + double height; + unsigned long age; +}; + +static struct location myloc; + +CTRL_CMD_DEFINE(net_loc, "location"); +int get_net_loc(struct ctrl_cmd *cmd, void *data) +{ + cmd->reply = talloc_asprintf(cmd, "%lu,%f,%f,%f", myloc.age, myloc.lat, myloc.lon, myloc.height); + if (!cmd->reply) { + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; + } + + return CTRL_CMD_REPLY; +} + +int set_net_loc(struct ctrl_cmd *cmd, void *data) +{ + char *saveptr, *lat, *lon, *height, *age, *tmp; + + tmp = talloc_strdup(cmd, cmd->value); + if (!tmp) + goto oom; + + + age = strtok_r(tmp, ",", &saveptr); + lat = strtok_r(NULL, ",", &saveptr); + lon = strtok_r(NULL, ",", &saveptr); + height = strtok_r(NULL, "\0", &saveptr); + + myloc.age = atol(age); + myloc.lat = atof(lat); + myloc.lon = atof(lon); + myloc.height = atof(height); + talloc_free(tmp); + + return get_net_loc(cmd, data); +oom: + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; +} + +int verify_net_loc(struct ctrl_cmd *cmd, const char *value, void *data) +{ + char *saveptr, *latstr, *lonstr, *heightstr, *agestr, *tmp; + int ret = 0; + unsigned long age; + double lat, lon, height; + + tmp = talloc_strdup(cmd, value); + if (!tmp) + return 1; + + agestr = strtok_r(tmp, ",", &saveptr); + latstr = strtok_r(NULL, ",", &saveptr); + lonstr = strtok_r(NULL, ",", &saveptr); + heightstr = strtok_r(NULL, "\0", &saveptr); + + if ((agestr == 0) || (latstr == 0) || (lonstr == 0) || (heightstr == 0)) + ret = 1; + + age = atol(agestr); + lat = atof(latstr); + lon = atof(lonstr); + height = atof(heightstr); + talloc_free(tmp); + + if ((age == 0) || (lat < -90) || (lat > 90) || (lon < -180) || (lon > 180)) + return 1; + + return ret; +} + +CTRL_CMD_DEFINE(trx_rf_lock, "rf_locked"); +int get_trx_rf_lock(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts_trx *trx = cmd->node; + if (!trx) { + cmd->reply = "trx not found."; + return CTRL_CMD_ERROR; + } + + cmd->reply = talloc_asprintf(cmd, "%u", trx->nm_state.administrative == NM_STATE_LOCKED ? 1 : 0); + return CTRL_CMD_REPLY; +} + +int set_trx_rf_lock(struct ctrl_cmd *cmd, void *data) +{ + int locked = atoi(cmd->value); + struct gsm_bts_trx *trx = cmd->node; + if (!trx) { + cmd->reply = "trx not found."; + return CTRL_CMD_ERROR; + } + + gsm_trx_lock_rf(trx, locked); + + return get_trx_rf_lock(cmd, data); +} + +int verify_trx_rf_lock(struct ctrl_cmd *cmd, const char *value, void *data) +{ + int locked = atoi(cmd->value); + + if ((locked != 0) && (locked != 1)) + return 1; + + return 0; +} + +CTRL_CMD_DEFINE(net_rf_lock, "rf_locked"); +int get_net_rf_lock(struct ctrl_cmd *cmd, void *data) +{ + cmd->reply = "get only works for the individual trx properties."; + return CTRL_CMD_ERROR; +} + +int set_net_rf_lock(struct ctrl_cmd *cmd, void *data) +{ + int locked = atoi(cmd->value); + struct gsm_network *net = cmd->node; + struct gsm_bts *bts; + if (!net) { + cmd->reply = "net not found."; + return CTRL_CMD_ERROR; + } + + llist_for_each_entry(bts, &net->bts_list, list) { + struct gsm_bts_trx *trx; + llist_for_each_entry(trx, &bts->trx_list, list) { + gsm_trx_lock_rf(trx, locked); + } + } + + cmd->reply = talloc_asprintf(cmd, "%u", locked); + if (!cmd->reply) { + cmd->reply = "OOM."; + return CTRL_CMD_ERROR; + } + + return CTRL_CMD_REPLY; +} + +int verify_net_rf_lock(struct ctrl_cmd *cmd, const char *value, void *data) +{ + int locked = atoi(cmd->value); + + if ((locked != 0) && (locked != 1)) + return 1; + + return 0; +} + int main(int argc, char **argv) { struct osmo_msc_data *data; @@ -204,6 +367,11 @@ int main(int argc, char **argv) } bsc_api_init(bsc_gsmnet, osmo_bsc_api()); + controlif_setup(bsc_gsmnet, 4249); + ctrl_cmd_install(CTRL_NODE_NET, &cmd_net_loc); + ctrl_cmd_install(CTRL_NODE_NET, &cmd_net_rf_lock); + ctrl_cmd_install(CTRL_NODE_TRX, &cmd_trx_rf_lock); + data = bsc_gsmnet->msc_data; if (rf_ctl) bsc_replace_string(data, &data->rf_ctrl_name, rf_ctl); diff --git a/openbsc/src/osmo-bsc/osmo_bsc_msc.c b/openbsc/src/osmo-bsc/osmo_bsc_msc.c index 445ce12fe..f4d6cf2eb 100644 --- a/openbsc/src/osmo-bsc/osmo_bsc_msc.c +++ b/openbsc/src/osmo-bsc/osmo_bsc_msc.c @@ -21,12 +21,15 @@ */ #include <openbsc/bsc_nat.h> +#include <openbsc/control_cmd.h> #include <openbsc/debug.h> #include <openbsc/gsm_data.h> #include <openbsc/ipaccess.h> #include <openbsc/osmo_msc_data.h> #include <openbsc/signal.h> +#include <osmocom/core/talloc.h> + #include <osmocom/gsm/gsm0808.h> #include <osmocom/sccp/sccp.h> @@ -189,6 +192,35 @@ static int msc_alink_do_write(struct osmo_fd *fd, struct msgb *msg) return ret; } +static void handle_ctrl(struct osmo_msc_data *msc, struct msgb *msg) +{ + int ret; + struct ctrl_cmd *cmd; + + cmd = ctrl_cmd_parse(msc->msc_con, msg); + if (!cmd) { + LOGP(DMSC, LOGL_ERROR, "Failed to parse control message.\n"); + cmd = talloc_zero(msc->msc_con, struct ctrl_cmd); + if (!cmd) { + LOGP(DMSC, LOGL_ERROR, "OOM!\n"); + return; + } + cmd->type = CTRL_TYPE_ERROR; + cmd->id = "err"; + cmd->reply = "Failed to parse control message."; + + ctrl_cmd_send(&msc->msc_con->write_queue, cmd); + talloc_free(cmd); + + return; + } + + ret = ctrl_cmd_handle(cmd, msc->network); + if (ret != CTRL_CMD_HANDLED) + ctrl_cmd_send(&msc->msc_con->write_queue, cmd); + talloc_free(cmd); +} + static void osmo_ext_handle(struct osmo_msc_data *msc, struct msgb *msg) { struct ipaccess_head *hh; @@ -206,6 +238,8 @@ static void osmo_ext_handle(struct osmo_msc_data *msc, struct msgb *msg) mgcp_forward(msc, msg); else if (hh_ext->proto == IPAC_PROTO_EXT_LAC) send_lacs(msc->network, msc->msc_con); + else if (hh_ext->proto == IPAC_PROTO_EXT_CTRL) + handle_ctrl(msc, msg); } static int ipaccess_a_fd_cb(struct osmo_fd *bfd) diff --git a/openbsc/src/osmo-bsc_nat/Makefile.am b/openbsc/src/osmo-bsc_nat/Makefile.am index 03fe62b4b..eea720e28 100644 --- a/openbsc/src/osmo-bsc_nat/Makefile.am +++ b/openbsc/src/osmo-bsc_nat/Makefile.am @@ -12,4 +12,5 @@ osmo_bsc_nat_LDADD = $(top_builddir)/src/libcommon/libcommon.a \ $(top_builddir)/src/libbsc/libbsc.a \ $(top_builddir)/src/libabis/libabis.a \ $(top_builddir)/src/libtrau/libtrau.a \ + $(top_builddir)/src/libctrl/libctrl.a \ -lrt $(LIBOSMOSCCP_LIBS) diff --git a/openbsc/src/osmo-bsc_nat/bsc_nat.c b/openbsc/src/osmo-bsc_nat/bsc_nat.c index e0eb635f0..0f865129f 100644 --- a/openbsc/src/osmo-bsc_nat/bsc_nat.c +++ b/openbsc/src/osmo-bsc_nat/bsc_nat.c @@ -39,6 +39,7 @@ #include <openbsc/bsc_msc.h> #include <openbsc/bsc_nat.h> #include <openbsc/bsc_nat_sccp.h> +#include <openbsc/control_cmd.h> #include <openbsc/ipaccess.h> #include <openbsc/abis_nm.h> #include <openbsc/socket.h> @@ -79,6 +80,7 @@ static struct bsc_nat *nat; static void bsc_send_data(struct bsc_connection *bsc, const uint8_t *data, unsigned int length, int); static void msc_send_reset(struct bsc_msc_connection *con); static void bsc_stat_reject(int filter, struct bsc_connection *bsc, int normal); +static void bsc_del_pending(struct bsc_cmd_list *pending); struct bsc_config *bsc_config_num(struct bsc_nat *nat, int num) { @@ -845,6 +847,7 @@ static int ipaccess_msc_write_cb(struct osmo_fd *bfd, struct msgb *msg) void bsc_close_connection(struct bsc_connection *connection) { struct sccp_connections *sccp_patch, *tmp; + struct bsc_cmd_list *cmd_entry, *cmd_tmp; struct rate_ctr *ctr = NULL; /* stop the timeout timer */ @@ -872,6 +875,14 @@ void bsc_close_connection(struct bsc_connection *connection) sccp_connection_destroy(sccp_patch); } + /* Reply to all outstanding commands */ + llist_for_each_entry_safe(cmd_entry, cmd_tmp, &connection->cmd_pending, list_entry) { + cmd_entry->cmd->type = CTRL_TYPE_ERROR; + cmd_entry->cmd->reply = "BSC closed the connection"; + ctrl_cmd_send(&cmd_entry->ccon->write_queue, cmd_entry->cmd); + bsc_del_pending(cmd_entry); + } + /* close endpoints allocated by this BSC */ bsc_mgcp_clear_endpoints_for(connection); @@ -1147,12 +1158,36 @@ exit3: return -1; } +static struct bsc_cmd_list *bsc_get_pending(struct bsc_connection *bsc, char *id_str) +{ + struct bsc_cmd_list *cmd_entry; + int id = atoi(id_str); + if (id == 0) + return NULL; + + llist_for_each_entry(cmd_entry, &bsc->cmd_pending, list_entry) { + if (cmd_entry->nat_id == id) { + return cmd_entry; + } + } + return NULL; +} + +static void bsc_del_pending(struct bsc_cmd_list *pending) +{ + llist_del(&pending->list_entry); + osmo_timer_del(&pending->timeout); + talloc_free(pending->cmd); + talloc_free(pending); +} + static int ipaccess_bsc_read_cb(struct osmo_fd *bfd) { int error; struct bsc_connection *bsc = bfd->data; struct msgb *msg = ipaccess_read_msg(bfd, &error); struct ipaccess_head *hh; + struct ipaccess_head_ext *hh_ext; if (!msg) { if (error == 0) @@ -1185,6 +1220,80 @@ static int ipaccess_bsc_read_cb(struct osmo_fd *bfd) msgb_free(msg); return 0; } + /* Message contains the ipaccess_head_ext header, investigate further */ + } else if (hh->proto == IPAC_PROTO_OSMO && + msg->len > sizeof(*hh) + sizeof(*hh_ext)) { + + hh_ext = (struct ipaccess_head_ext *) hh->data; + /* l2h is where the actual command data is expected */ + msg->l2h = hh_ext->data; + + if (hh_ext->proto == IPAC_PROTO_EXT_CTRL) { + struct ctrl_cmd *cmd; + struct bsc_cmd_list *pending; + char *var, *id; + + cmd = ctrl_cmd_parse(bsc, msg); + msgb_free(msg); + + if (!cmd) { + cmd = talloc_zero(bsc, struct ctrl_cmd); + if (!cmd) { + LOGP(DNAT, LOGL_ERROR, "OOM!\n"); + return 0; + } + cmd->type = CTRL_TYPE_ERROR; + cmd->id = "err"; + cmd->reply = "Failed to parse command."; + ctrl_cmd_send(&bsc->write_queue, cmd); + talloc_free(cmd); + return 0; + } + + if (bsc->cfg) { + if (!llist_empty(&bsc->cfg->lac_list)) { + if (cmd->variable) { + var = talloc_asprintf(cmd, "bsc.%i.%s", ((struct bsc_lac_entry *)bsc->cfg->lac_list.next)->lac, + cmd->variable); + if (!var) { + cmd->type = CTRL_TYPE_ERROR; + cmd->reply = "OOM"; + goto err; + } + talloc_free(cmd->variable); + cmd->variable = var; + } + + /* Find the pending command */ + pending = bsc_get_pending(bsc, cmd->id); + if (pending) { + id = talloc_strdup(cmd, pending->cmd->id); + if (!id) { + cmd->type = CTRL_TYPE_ERROR; + cmd->reply = "OOM"; + goto err; + } + cmd->id = id; + ctrl_cmd_send(&pending->ccon->write_queue, cmd); + bsc_del_pending(pending); + } else { + /* We need to handle TRAPS here */ + if ((cmd->type != CTRL_TYPE_ERROR) && (cmd->type != CTRL_TYPE_TRAP)) { + LOGP(DNAT, LOGL_NOTICE, "Got control message from BSC without pending entry\n"); + cmd->type = CTRL_TYPE_ERROR; + cmd->reply = "No request outstanding"; + goto err; + } + } + } + } + talloc_free(cmd); + return 0; +err: + ctrl_cmd_send(&bsc->write_queue, cmd); + talloc_free(cmd); + return 0; + } } /* FIXME: Currently no PONG is sent to the BSC */ @@ -1265,7 +1374,10 @@ static int ipaccess_listen_bsc_cb(struct osmo_fd *bfd, unsigned int what) LOGP(DNAT, LOGL_NOTICE, "BSC connection on %d with IP: %s\n", fd, inet_ntoa(sa.sin_addr)); + llist_add(&bsc->list_entry, &nat->bsc_connections); + bsc->last_id = 0; + send_id_ack(bsc); send_id_req(bsc); send_mgcp_reset(bsc); @@ -1412,6 +1524,170 @@ static struct vty_app_info vty_info = { .is_config_node = bsc_vty_is_config_node, }; +static int get_next_free_bsc_id(struct bsc_connection *bsc) +{ + int new_id, overflow = 0; + struct bsc_cmd_list *pending; + + new_id = bsc->last_id; + do { + new_id++; + if (new_id <= 0) { + new_id = 1; + overflow++; + } + + llist_for_each_entry(pending, &bsc->cmd_pending, list_entry) { + if (pending->nat_id == new_id) + continue; + } + + /* ID is not in use */ + break; + } while ((new_id != bsc->last_id) && (overflow < 2)); + + if ((new_id == bsc->last_id) || (overflow == 2)) { + return -1; + } else { + bsc->last_id = new_id; + return new_id; + } +} + +static void pending_timeout_cb(void *data) +{ + struct bsc_cmd_list *pending = data; + LOGP(DNAT, LOGL_ERROR, "Command timed out\n"); + pending->cmd->type = CTRL_TYPE_ERROR; + pending->cmd->reply = "Command timed out"; + ctrl_cmd_send(&pending->ccon->write_queue, pending->cmd); + + bsc_del_pending(pending); +} + +static void ctrl_conn_closed_cb(struct ctrl_connection *connection) +{ + struct bsc_connection *bsc; + struct bsc_cmd_list *pending, *tmp; + + llist_for_each_entry(bsc, &nat->bsc_connections, list_entry) { + llist_for_each_entry_safe(pending, tmp, &bsc->cmd_pending, list_entry) { + if (pending->ccon == connection) + bsc_del_pending(pending); + } + } +} + +static int forward_to_bsc(struct ctrl_cmd *cmd) +{ + int ret = CTRL_CMD_HANDLED; + struct ctrl_cmd *bsc_cmd = NULL; + struct bsc_connection *bsc; + struct bsc_cmd_list *pending; + unsigned int lac; + char *lac_str, *tmp, *saveptr; + + /* Skip over the beginning (bsc.) */ + tmp = strtok_r(cmd->variable, ".", &saveptr); + lac_str = strtok_r(NULL, ".", &saveptr); + if (!lac_str) { + cmd->reply = "command incomplete"; + goto err; + } + lac = atoi(lac_str); + + tmp = strtok_r(NULL, "\0", &saveptr); + if (!tmp) { + cmd->reply = "command incomplete"; + goto err; + } + + llist_for_each_entry(bsc, &nat->bsc_connections, list_entry) { + if (!bsc->cfg) + continue; + if (!bsc->authenticated) + continue; + if (bsc_config_handles_lac(bsc->cfg, lac)) { + /* Add pending command to list */ + pending = talloc_zero(bsc, struct bsc_cmd_list); + if (!pending) { + cmd->reply = "OOM"; + goto err; + } + + pending->nat_id = get_next_free_bsc_id(bsc); + if (pending->nat_id < 0) { + cmd->reply = "No free ID found"; + goto err; + } + + bsc_cmd = ctrl_cmd_cpy(bsc, cmd); + if (!bsc_cmd) { + cmd->reply = "Could not forward command"; + goto err; + } + + talloc_free(bsc_cmd->id); + bsc_cmd->id = talloc_asprintf(bsc_cmd, "%i", pending->nat_id); + if (!bsc_cmd->id) { + cmd->reply = "OOM"; + goto err; + } + + talloc_free(bsc_cmd->variable); + bsc_cmd->variable = talloc_strdup(bsc_cmd, tmp); + if (!bsc_cmd->variable) { + cmd->reply = "OOM"; + goto err; + } + + if (ctrl_cmd_send(&bsc->write_queue, bsc_cmd)) { + cmd->reply = "Sending failed"; + goto err; + } + pending->ccon = cmd->ccon; + pending->ccon->closed_cb = ctrl_conn_closed_cb; + pending->cmd = cmd; + + /* Setup the timeout */ + pending->timeout.data = pending; + pending->timeout.cb = pending_timeout_cb; + /* TODO: Make timeout configurable */ + osmo_timer_schedule(&pending->timeout, 10, 0); + llist_add_tail(&pending->list_entry, &bsc->cmd_pending); + + goto done; + } + } + /* We end up here if there's no bsc to handle our LAC */ + cmd->reply = "no BSC with this LAC"; +err: + ret = CTRL_CMD_ERROR; +done: + if (bsc_cmd) + talloc_free(bsc_cmd); + return ret; + +} + +CTRL_CMD_DEFINE(fwd_cmd, "bsc *"); +int get_fwd_cmd(struct ctrl_cmd *cmd, void *data) +{ + return forward_to_bsc(cmd); +} + +int set_fwd_cmd(struct ctrl_cmd *cmd, void *data) +{ + return forward_to_bsc(cmd); +} + +int verify_fwd_cmd(struct ctrl_cmd *cmd, const char *value, void *data) +{ + return 0; +} + +extern int controlif_setup(struct gsm_network *gsmnet, uint16_t port); + int main(int argc, char **argv) { int rc; @@ -1471,6 +1747,9 @@ int main(int argc, char **argv) exit(1); } + controlif_setup(NULL, 4250); + ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_fwd_cmd); + nat->msc_con->connection_loss = msc_connection_was_lost; nat->msc_con->connected = msc_connection_connected; nat->msc_con->write_queue.read_cb = ipaccess_msc_read_cb; diff --git a/openbsc/src/osmo-bsc_nat/bsc_nat_utils.c b/openbsc/src/osmo-bsc_nat/bsc_nat_utils.c index 4834340c8..8658c3d9b 100644 --- a/openbsc/src/osmo-bsc_nat/bsc_nat_utils.c +++ b/openbsc/src/osmo-bsc_nat/bsc_nat_utils.c @@ -129,6 +129,7 @@ struct bsc_connection *bsc_connection_alloc(struct bsc_nat *nat) con->nat = nat; osmo_wqueue_init(&con->write_queue, 100); + INIT_LLIST_HEAD(&con->cmd_pending); return con; } diff --git a/openbsc/src/osmo-nitb/Makefile.am b/openbsc/src/osmo-nitb/Makefile.am index 9cb92cf2c..1b813a271 100644 --- a/openbsc/src/osmo-nitb/Makefile.am +++ b/openbsc/src/osmo-nitb/Makefile.am @@ -11,4 +11,6 @@ osmo_nitb_LDADD = -ldl $(LIBCRYPT) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) \ $(top_builddir)/src/libbsc/libbsc.a \ $(top_builddir)/src/libtrau/libtrau.a \ $(top_builddir)/src/libabis/libabis.a \ - $(top_builddir)/src/libcommon/libcommon.a -ldbi + $(top_builddir)/src/libcommon/libcommon.a \ + $(top_builddir)/src/libctrl/libctrl.a \ + -ldbi diff --git a/openbsc/src/osmo-nitb/bsc_hack.c b/openbsc/src/osmo-nitb/bsc_hack.c index e548a9569..cbc619015 100644 --- a/openbsc/src/osmo-nitb/bsc_hack.c +++ b/openbsc/src/osmo-nitb/bsc_hack.c @@ -207,6 +207,8 @@ static void db_sync_timer_cb(void *data) void talloc_ctx_init(void); +extern int controlif_setup(struct gsm_network *gsmnet, uint16_t port); + extern enum node_type bsc_vty_go_parent(struct vty *vty); static struct vty_app_info vty_info = { @@ -251,6 +253,7 @@ int main(int argc, char **argv) exit(1); bsc_api_init(bsc_gsmnet, msc_bsc_api()); + controlif_setup(bsc_gsmnet, 4249); /* seed the PRNG */ srand(time(NULL)); |