From 1264cb404f460576690f52fdc8032b74de0bc04e Mon Sep 17 00:00:00 2001 From: Daniel Willmann Date: Thu, 21 Oct 2010 15:00:36 +0200 Subject: Add libctrl, an SNMP-like control interface In contrast to the VTY interface the control interface is meant to be used by programs. This patch adds basic support, no commands are defined. --- openbsc/src/libctrl/control_if.c | 369 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 369 insertions(+) create mode 100644 openbsc/src/libctrl/control_if.c (limited to 'openbsc/src/libctrl/control_if.c') diff --git a/openbsc/src/libctrl/control_if.c b/openbsc/src/libctrl/control_if.c new file mode 100644 index 00000000..a607b10b --- /dev/null +++ b/openbsc/src/libctrl/control_if.c @@ -0,0 +1,369 @@ +/* SNMP-like status interface + * + * (C) 2010-2011 by Daniel Willmann + * (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 +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include + +#include +#include + +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;inode = 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; jreply = "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; +} + +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; + } + + return ret; +} -- cgit v1.2.3