diff options
| author | Harald Welte <laforge@gnumonks.org> | 2014-08-20 19:18:15 +0200 | 
|---|---|---|
| committer | Harald Welte <laforge@gnumonks.org> | 2014-08-21 15:34:17 +0200 | 
| commit | dda42251532c347af66ccd6f515745ab83eb8bd5 (patch) | |
| tree | ce3eda356832fe89284e84685629b5bf6fe9de9e /src/ctrl | |
| parent | 5ad742d56e9f0d7d3e442ddde17bba94d6b1e6c4 (diff) | |
libctr: rename/move control interface to libosmocore naming scheme
Diffstat (limited to 'src/ctrl')
| -rw-r--r-- | src/ctrl/Makefile.am | 7 | ||||
| -rw-r--r-- | src/ctrl/control_cmd.c | 491 | ||||
| -rw-r--r-- | src/ctrl/control_if.c | 573 | 
3 files changed, 1071 insertions, 0 deletions
diff --git a/src/ctrl/Makefile.am b/src/ctrl/Makefile.am new file mode 100644 index 00000000..4f039c82 --- /dev/null +++ b/src/ctrl/Makefile.am @@ -0,0 +1,7 @@ +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir) +AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(COVERAGE_CFLAGS) +AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(LIBOSMOABIS_LIBS) $(COVERAGE_LDFLAGS) + +noinst_LIBRARIES = libctrl.a + +libctrl_a_SOURCES =	control_if.c control_cmd.c diff --git a/src/ctrl/control_cmd.c b/src/ctrl/control_cmd.c new file mode 100644 index 00000000..44cfa485 --- /dev/null +++ b/src/ctrl/control_cmd.c @@ -0,0 +1,491 @@ +/* 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; +				/* If verify() set an appropriate error message, don't change it. */ +				if (!command->reply) +					command->reply = "Value failed verification."; +				goto out; +			} +		} +		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(DCTRL, 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(DCTRL, 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_create(void *ctx, enum ctrl_type type) +{ +	struct ctrl_cmd *cmd; + +	cmd = talloc_zero(ctx, struct ctrl_cmd); +	if (!cmd) +		return NULL; + +	cmd->type = type; +	return cmd; +} + +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(DCTRL, 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(DCTRL, LOGL_NOTICE, "GET Command incomplete\n"); +				goto err; +			} +			cmd->variable = talloc_strdup(cmd, var); +			LOGP(DCTRL, LOGL_DEBUG, "Command: GET %s\n", cmd->variable); +			break; +		case CTRL_TYPE_SET: +			var = strtok_r(NULL, " ", &saveptr); +			val = strtok_r(NULL, "\n", &saveptr); +			if (!var || !val) { +				cmd->type = CTRL_TYPE_ERROR; +				cmd->reply = "SET incomplete"; +				LOGP(DCTRL, 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(DCTRL, 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(DCTRL, 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(DCTRL, 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(DCTRL, 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(DCTRL, 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(DCTRL, 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(DCTRL, 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(DCTRL, 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(DCTRL, LOGL_NOTICE, "Unknown command type %i\n", cmd->type); +		goto err; +		break; +	} + +	return msg; + +err: +	msgb_free(msg); +	return NULL; +} diff --git a/src/ctrl/control_if.c b/src/ctrl/control_if.c new file mode 100644 index 00000000..ca59d8c6 --- /dev/null +++ b/src/ctrl/control_if.c @@ -0,0 +1,573 @@ +/* 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 <inttypes.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/control_if.h> +#include <openbsc/debug.h> +#include <openbsc/gsm_data.h> +#include <openbsc/ipaccess.h> +#include <openbsc/socket.h> +#include <osmocom/abis/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> + +#include <osmocom/abis/e1_input.h> +#include <osmocom/abis/ipa.h> + +vector ctrl_node_vec; + +/* Send command to all  */ +int ctrl_cmd_send_to_all(struct ctrl_handle *ctrl, struct ctrl_cmd *cmd) +{ +	struct ctrl_connection *ccon; +	int ret = 0; + +	llist_for_each_entry(ccon, &ctrl->ccon_list, list_entry) { +		if (ccon == cmd->ccon) +			continue; +		if (ctrl_cmd_send(&ccon->write_queue, cmd)) +			ret++; +	} +	return ret; +} + +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(DCTRL, 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(DCTRL, LOGL_ERROR, "Failed to enqueue the command.\n"); +		msgb_free(msg); +	} +	return ret; +} + +struct ctrl_cmd *ctrl_cmd_trap(struct ctrl_cmd *cmd) +{ +	struct ctrl_cmd *trap; + +	trap = ctrl_cmd_cpy(tall_bsc_ctx, cmd); +	if (!trap) +		return NULL; + +	trap->ccon = cmd->ccon; +	trap->type = CTRL_TYPE_TRAP; +	return trap; +} + +static void control_close_conn(struct ctrl_connection *ccon) +{ +	osmo_wqueue_clear(&ccon->write_queue); +	close(ccon->write_queue.bfd.fd); +	osmo_fd_unregister(&ccon->write_queue.bfd); +	llist_del(&ccon->list_entry); +	if (ccon->closed_cb) +		ccon->closed_cb(ccon); +	msgb_free(ccon->pending_msg); +	talloc_free(ccon); +} + +static int handle_control_read(struct osmo_fd * bfd) +{ +	int ret = -1; +	struct osmo_wqueue *queue; +	struct ctrl_connection *ccon; +	struct ipaccess_head *iph; +	struct ipaccess_head_ext *iph_ext; +	struct msgb *msg = NULL; +	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); + +	ret = ipa_msg_recv_buffered(bfd->fd, &msg, &ccon->pending_msg); +	if (ret <= 0) { +		if (ret == -EAGAIN) +			return 0; +		if (ret == 0) +			LOGP(DCTRL, LOGL_INFO, "The control connection was closed\n"); +		else +			LOGP(DCTRL, LOGL_ERROR, "Failed to parse ip access message: %d\n", ret); + +		goto err; +	} + +	if (msg->len < sizeof(*iph) + sizeof(*iph_ext)) { +		LOGP(DCTRL, LOGL_ERROR, "The message is too short.\n"); +		goto err; +	} + +	iph = (struct ipaccess_head *) msg->data; +	if (iph->proto != IPAC_PROTO_OSMO) { +		LOGP(DCTRL, 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(DCTRL, 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->handler(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(DCTRL, 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(DCTRL, 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_handle *ctrl; +	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(DCTRL, 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(DCTRL, LOGL_ERROR, "Failed to allocate.\n"); +		close(fd); +		return -1; +	} + +	ctrl = listen_bfd->data; +	ccon->write_queue.bfd.data = ctrl; +	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(DCTRL, LOGL_ERROR, "Could not register FD.\n"); +		close(ccon->write_queue.bfd.fd); +		talloc_free(ccon); +	} + +	llist_add(&ccon->list_entry, &ctrl->ccon_list); + +	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 %"PRIu64, +			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 *"); +static 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, "%"PRIu64, get_rate_ctr_value(ctr, intv)); +	if (!cmd->reply) +		goto oom; + +	return CTRL_CMD_REPLY; +oom: +	cmd->reply = "OOM"; +err: +	return CTRL_CMD_ERROR; +} + +static int set_rate_ctr(struct ctrl_cmd *cmd, void *data) +{ +	cmd->reply = "Can't set rate counter."; + +	return CTRL_CMD_ERROR; +} + +static int verify_rate_ctr(struct ctrl_cmd *cmd, const char *value, void *data) +{ +	return 0; +} + +/* counter */ +CTRL_CMD_DEFINE(counter, "counter *"); +static 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; +} + +static int set_counter(struct ctrl_cmd *cmd, void *data) +{ + +	cmd->reply = "Can't set counter."; + +	return CTRL_CMD_ERROR; +} + +static int verify_counter(struct ctrl_cmd *cmd, const char *value, void *data) +{ +	return 0; +} + +struct ctrl_handle *controlif_setup(struct gsm_network *gsmnet, uint16_t port, +					ctrl_cmd_handler handler) +{ +	int ret; +	struct ctrl_handle *ctrl; + +	ctrl = talloc_zero(tall_bsc_ctx, struct ctrl_handle); +	if (!ctrl) +		return NULL; + +	INIT_LLIST_HEAD(&ctrl->ccon_list); + +	ctrl->gsmnet = gsmnet; +	ctrl->handler = handler; + +	ctrl_node_vec = vector_init(5); +	if (!ctrl_node_vec) +		goto err; + +	/* Listen for control connections */ +	ret = make_sock(&ctrl->listen_fd, IPPROTO_TCP, INADDR_LOOPBACK, port, +			0, listen_fd_cb, ctrl); +	if (ret < 0) +		goto err_vec; + +	ret = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_rate_ctr); +	if (ret) +		goto err_vec; +	ret = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_counter); +	if (ret) +		goto err_vec; + +	return ctrl; +err_vec: +	vector_free(ctrl_node_vec); +	ctrl_node_vec = NULL; +err: +	talloc_free(ctrl); +	return NULL; +}  | 
