changeset 20:2230a763713f

ctrl-client: import from osmo-sysmon git repo
author Mychaela Falconia <falcon@freecalypso.org>
date Sun, 17 Dec 2023 08:43:13 +0000
parents 9ff041d85da5
children 80849380395d
files ctrl-client/osmo-ctrl-client.c ctrl-client/simple_ctrl.c ctrl-client/simple_ctrl.h
diffstat 3 files changed, 439 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ctrl-client/osmo-ctrl-client.c	Sun Dec 17 08:43:13 2023 +0000
@@ -0,0 +1,86 @@
+/* Simple command-line client against the Osmocom CTRL interface */
+
+/* (C) 2018 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved.
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ *  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.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "simple_ctrl.h"
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/application.h>
+
+static struct log_info log_info = {};
+
+static void exit_help(void)
+{
+	printf("Usage:\n");
+	printf("\tosmo-ctrl-client HOST PORT get VARIABLE\n");
+	printf("\tosmo-ctrl-client HOST PORT set VARIABLE VALUE\n");
+	printf("\tosmo-ctrl-client HOST PORT monitor\n");
+	exit(2);
+}
+
+int main(int argc, char **argv)
+{
+	struct simple_ctrl_handle *sch;
+	const char *host;
+	uint16_t port;
+	int rc;
+
+	if (argc < 4)
+		exit_help();
+
+	host = argv[1];
+	port = atoi(argv[2]);
+
+	osmo_init_logging2(NULL, &log_info);
+
+	sch = simple_ctrl_open(NULL, host, port, 1000);
+	if (!sch)
+		exit(1);
+
+	if (!strcmp(argv[3], "get")) {
+		char *val;
+		if (argc < 5)
+			exit_help();
+		val = simple_ctrl_get(sch, argv[4]);
+		if (!val)
+			exit(2);
+		printf("%s\n", val);
+	} else if (!strcmp(argv[3], "set")) {
+		if (argc < 6)
+			exit_help();
+		rc = simple_ctrl_set(sch, argv[4], argv[5]);
+		if (rc < 0)
+			exit(1);
+	} else if (!strcmp(argv[3], "monitor")) {
+		simple_ctrl_set_timeout(sch, 0);
+		while (true) {
+			struct msgb *msg = simple_ctrl_receive(sch);
+			if (!msg)
+				exit(1);
+			printf("%s", (char *) msgb_l2(msg));
+			msgb_free(msg);
+		}
+	} else
+		exit_help();
+
+	exit(0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ctrl-client/simple_ctrl.c	Sun Dec 17 08:43:13 2023 +0000
@@ -0,0 +1,337 @@
+/* Simple, blocking client API against the Osmocom CTRL interface */
+
+/* (C) 2018 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved.
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ *  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.
+ */
+
+#include <unistd.h>
+#include <stdint.h>
+#include <talloc.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+
+#include <netinet/in.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/gsm/ipa.h>
+#include <osmocom/gsm/protocol/ipaccess.h>
+
+#include "client.h"
+#include "simple_ctrl.h"
+
+#define CTRL_ERR(sch, fmt, args...) \
+	fprintf(stderr, "CTRL %s error: " fmt, make_authority(sch, &sch->cfg), ##args)
+
+/***********************************************************************
+ * blocking I/O with timeout helpers
+ ***********************************************************************/
+
+static struct timeval *timeval_from_msec(uint32_t tout_msec)
+{
+	static struct timeval tout;
+
+	if (tout_msec == 0)
+		return NULL;
+	tout.tv_sec = tout_msec/1000;
+	tout.tv_usec = (tout_msec%1000)*1000;
+
+	return &tout;
+}
+
+static ssize_t read_timeout(int fd, void *buf, size_t count, uint32_t tout_msec)
+{
+	fd_set readset;
+	int rc;
+
+	FD_ZERO(&readset);
+	FD_SET(fd, &readset);
+
+	rc = select(fd+1, &readset, NULL, NULL, timeval_from_msec(tout_msec));
+	if (rc < 0)
+		return rc;
+
+	if (FD_ISSET(fd, &readset))
+		return read(fd, buf, count);
+
+	return -ETIMEDOUT;
+}
+
+static ssize_t write_timeout(int fd, const void *buf, size_t count, uint32_t tout_msec)
+{
+	fd_set writeset;
+	int rc;
+
+	FD_ZERO(&writeset);
+	FD_SET(fd, &writeset);
+
+	rc = select(fd+1, NULL, &writeset, NULL, timeval_from_msec(tout_msec));
+	if (rc < 0)
+		return rc;
+
+	if (FD_ISSET(fd, &writeset))
+		return write(fd, buf, count);
+
+	return -ETIMEDOUT;
+}
+
+
+/***********************************************************************
+ * actual CTRL client API
+ ***********************************************************************/
+
+struct simple_ctrl_handle {
+	int fd;
+	uint32_t next_id;
+	uint32_t tout_msec;
+	struct host_cfg cfg;
+};
+
+struct simple_ctrl_handle *simple_ctrl_open(void *ctx, const char *host, uint16_t dport,
+					    uint32_t tout_msec)
+{
+	struct simple_ctrl_handle *sch = talloc_zero(ctx, struct simple_ctrl_handle);
+	fd_set writeset;
+	int off = 0;
+	int rc, fd;
+
+	if (!sch)
+		return NULL;
+
+	sch->cfg.name = talloc_strdup(sch, "simple-ctrl");
+	sch->cfg.remote_host = talloc_strdup(sch, host);
+	sch->cfg.remote_port = dport;
+
+	fd = osmo_sock_init(AF_INET, SOCK_STREAM, IPPROTO_TCP, host, dport,
+			    OSMO_SOCK_F_CONNECT | OSMO_SOCK_F_NONBLOCK);
+	if (fd < 0) {
+		CTRL_ERR(sch, "connecting socket: %s\n", strerror(errno));
+		return NULL;
+	}
+
+	/* wait until connect (or timeout) happens */
+	FD_ZERO(&writeset);
+	FD_SET(fd, &writeset);
+	rc = select(fd+1, NULL, &writeset, NULL, timeval_from_msec(tout_msec));
+	if (rc == 0) {
+		CTRL_ERR(sch, "timeout during connect\n");
+		goto out_close;
+	}
+	if (rc < 0) {
+		CTRL_ERR(sch, "error connecting socket: %s\n", strerror(errno));
+		goto out_close;
+	}
+
+	/* set FD blocking again */
+	if (ioctl(fd, FIONBIO, (unsigned char *)&off) < 0) {
+		CTRL_ERR(sch, "cannot set socket blocking: %s\n", strerror(errno));
+		goto out_close;
+	}
+
+	sch->fd = fd;
+	sch->tout_msec = tout_msec;
+	return sch;
+
+out_close:
+	close(fd);
+	return NULL;
+}
+
+void simple_ctrl_set_timeout(struct simple_ctrl_handle *sch, uint32_t tout_msec)
+{
+	sch->tout_msec = tout_msec;
+}
+
+void simple_ctrl_close(struct simple_ctrl_handle *sch)
+{
+	close(sch->fd);
+	talloc_free(sch);
+}
+
+static struct msgb *simple_ipa_receive(struct simple_ctrl_handle *sch)
+{
+	struct ipaccess_head hh;
+	struct msgb *resp;
+	int rc, len;
+
+	rc = read_timeout(sch->fd, (uint8_t *) &hh, sizeof(hh), sch->tout_msec);
+	if (rc < 0) {
+		CTRL_ERR(sch, "read(): %d\n", rc);
+		return NULL;
+	} else if (rc < sizeof(hh)) {
+		CTRL_ERR(sch, "short read (header)\n");
+		return NULL;
+	}
+	len = ntohs(hh.len);
+
+	resp = msgb_alloc(len+sizeof(hh)+1, "CTRL Rx");
+	if (!resp)
+		return NULL;
+	resp->l1h = msgb_put(resp, sizeof(hh));
+	memcpy(resp->l1h, (uint8_t *) &hh, sizeof(hh));
+
+	resp->l2h = resp->tail;
+	rc = read(sch->fd, resp->l2h, len);
+	if (rc < len) {
+		CTRL_ERR(sch, "short read (payload)\n");
+		msgb_free(resp);
+		return NULL;
+	}
+	msgb_put(resp, rc);
+
+	return resp;
+}
+
+struct msgb *simple_ctrl_receive(struct simple_ctrl_handle *sch)
+{
+	struct msgb *resp;
+	struct ipaccess_head *ih;
+	struct ipaccess_head_ext *ihe;
+	unsigned char *tmp;
+
+	/* loop until we've received a CTRL message */
+	while (true) {
+		resp = simple_ipa_receive(sch);
+		if (!resp)
+			return NULL;
+
+		ih = (struct ipaccess_head *) resp->l1h;
+		if (ih->proto == IPAC_PROTO_OSMO)
+			resp->l2h = resp->l2h+1;
+		ihe = (struct ipaccess_head_ext*) (resp->l1h + sizeof(*ih));
+		if (ih->proto == IPAC_PROTO_OSMO && ihe->proto == IPAC_PROTO_EXT_CTRL) {
+			/* Ensure data is NULL terminated */
+			tmp = msgb_put(resp, 1);
+			*tmp = '\0';
+			return resp;
+		} else {
+			CTRL_ERR(sch, "unknown IPA message %s\n", msgb_hexdump(resp));
+			msgb_free(resp);
+		}
+	}
+}
+
+static int simple_ctrl_send(struct simple_ctrl_handle *sch, struct msgb *msg)
+{
+	int rc;
+
+	ipa_prepend_header_ext(msg, IPAC_PROTO_EXT_CTRL);
+	ipa_prepend_header(msg, IPAC_PROTO_OSMO);
+
+	rc = write_timeout(sch->fd, msg->data, msg->len, sch->tout_msec);
+	if (rc < 0) {
+		CTRL_ERR(sch, "write(): %d\n", rc);
+		return rc;
+	} else if (rc < msg->len) {
+		CTRL_ERR(sch, "short write\n");
+		msgb_free(msg);
+		return -1;
+	} else {
+		msgb_free(msg);
+		return 0;
+	}
+}
+
+static struct msgb *simple_ctrl_xceive(struct simple_ctrl_handle *sch, struct msgb *msg)
+{
+	int rc;
+
+	rc = simple_ctrl_send(sch, msg);
+	if (rc < 0)
+		return NULL;
+
+	/* FIXME: ignore any TRAP */
+	return simple_ctrl_receive(sch);
+}
+
+char *simple_ctrl_get(struct simple_ctrl_handle *sch, const char *var)
+{
+	struct msgb *msg = msgb_alloc_headroom(512+8, 8, "CTRL GET");
+	struct msgb *resp;
+	unsigned int rx_id;
+	char *rx_var, *rx_val;
+	int rc;
+
+	if (!msg)
+		return NULL;
+
+	rc = msgb_printf(msg, "GET %u %s", sch->next_id++, var);
+	if (rc < 0) {
+		msgb_free(msg);
+		return NULL;
+	}
+	resp = simple_ctrl_xceive(sch, msg);
+	if (!resp)
+		return NULL;
+
+	rc = sscanf(msgb_l2(resp), "GET_REPLY %u %ms %ms", &rx_id, &rx_var, &rx_val);
+	if ((rc == 2) || (rc == 3)) {
+		/* If body is empty return an empty string */
+		if (rc == 2)
+			rx_val = strdup("");
+
+		if (rx_id == sch->next_id-1 && !strcmp(var, rx_var)) {
+			free(rx_var);
+			msgb_free(resp);
+			return rx_val;
+		}
+		free(rx_var);
+		free(rx_val);
+	} else {
+		CTRL_ERR(sch, "GET(%s) results in '%s'\n", var, (char *)msgb_l2(resp));
+	}
+
+	msgb_free(resp);
+	return NULL;
+}
+
+int simple_ctrl_set(struct simple_ctrl_handle *sch, const char *var, const char *val)
+{
+	struct msgb *msg = msgb_alloc_headroom(512+8, 8, "CTRL SET");
+	struct msgb *resp;
+	unsigned int rx_id;
+	char *rx_var, *rx_val;
+	int rc;
+
+	if (!msg)
+		return -1;
+
+	rc = msgb_printf(msg, "SET %u %s %s", sch->next_id++, var, val);
+	if (rc < 0) {
+		msgb_free(msg);
+		return -1;
+	}
+	resp = simple_ctrl_xceive(sch, msg);
+	if (!resp)
+		return -1;
+
+	if (sscanf(msgb_l2(resp), "SET_REPLY %u %ms %ms", &rx_id, &rx_var, &rx_val) == 3) {
+		if (rx_id == sch->next_id-1 && !strcmp(var, rx_var) && !strcmp(val, rx_val)) {
+			free(rx_val);
+			free(rx_var);
+			msgb_free(resp);
+			return 0;
+		} else {
+			free(rx_val);
+			free(rx_var);
+		}
+	} else {
+		CTRL_ERR(sch, "SET(%s=%s) results in '%s'\n", var, val, (char *) msgb_l2(resp));
+	}
+
+	msgb_free(resp);
+	return -1;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ctrl-client/simple_ctrl.h	Sun Dec 17 08:43:13 2023 +0000
@@ -0,0 +1,16 @@
+#pragma once
+
+#include <stdint.h>
+
+struct simple_ctrl_handle;
+
+struct simple_ctrl_handle *simple_ctrl_open(void *ctx, const char *host, uint16_t dport,
+					    uint32_t tout_msec);
+void simple_ctrl_close(struct simple_ctrl_handle *sch);
+
+void simple_ctrl_set_timeout(struct simple_ctrl_handle *sch, uint32_t tout_msec);
+struct msgb *simple_ctrl_receive(struct simple_ctrl_handle *sch);
+
+char *simple_ctrl_get(struct simple_ctrl_handle *sch, const char *var);
+int simple_ctrl_set(struct simple_ctrl_handle *sch, const char *var, const char *val);
+