summaryrefslogtreecommitdiffstats
path: root/src/vty
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2019-12-29 15:50:32 +0100
committertv <tv@krebsco.de>2019-12-29 15:50:32 +0100
commit8ec8e8a051c57230443e97279b274fcb620e9540 (patch)
tree9ab650bba2292671ce36a9d1fac782bc6862ba18 /src/vty
parentc6a8697800376a02b868cdea8fc1bf55f12798f1 (diff)
[WIP] prometheus module (with prometheus_vty)
Diffstat (limited to 'src/vty')
-rw-r--r--src/vty/Makefile.am3
-rw-r--r--src/vty/prometheus_vty.c679
2 files changed, 681 insertions, 1 deletions
diff --git a/src/vty/Makefile.am b/src/vty/Makefile.am
index abed92ac..450549d0 100644
--- a/src/vty/Makefile.am
+++ b/src/vty/Makefile.am
@@ -12,7 +12,8 @@ lib_LTLIBRARIES = libosmovty.la
libosmovty_la_SOURCES = buffer.c command.c vty.c vector.c utils.c \
telnet_interface.c logging_vty.c stats_vty.c \
fsm_vty.c talloc_ctx_vty.c \
- tdef_vty.c
+ tdef_vty.c \
+ prometheus_vty.c
libosmovty_la_LDFLAGS = -version-info $(LIBVERSION) -no-undefined
libosmovty_la_LIBADD = $(top_builddir)/src/libosmocore.la $(TALLOC_LIBS)
endif
diff --git a/src/vty/prometheus_vty.c b/src/vty/prometheus_vty.c
new file mode 100644
index 00000000..89652444
--- /dev/null
+++ b/src/vty/prometheus_vty.c
@@ -0,0 +1,679 @@
+//#include <sys/socket.h>
+//#include <netinet/in.h>
+#include <errno.h>
+//#include <stdlib.h>
+//#include <stdio.h>
+#include <stdarg.h> // TODO killme?
+#include <string.h>
+#include <unistd.h>
+#include <termios.h> // TODO killme
+//
+//#include <osmocom/core/msgb.h>
+//XXX[osmo_counter]#include <osmocom/core/linuxlist.h>
+//XXX[osmo_counter]#include <osmocom/core/counter.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/stat_item.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/signal.h>
+#include <osmocom/core/select.h>
+//
+#include <osmocom/vty/prometheus.h>
+#include <osmocom/vty/buffer.h>
+#include <osmocom/vty/command.h>
+
+#include <osmocom/core/rate_ctr.h>
+
+
+/* per connection data */
+LLIST_HEAD(prom_active_connections);
+
+static void *prom_ctx;
+
+/* Vector which store each vty structure. */
+static vector prom_vtyvec;
+
+/* per network data */
+static int prom_new_connection(struct osmo_fd *fd, unsigned int what);
+
+static struct osmo_fd server_socket = {
+ .when = OSMO_FD_READ,
+ .cb = prom_new_connection,
+ .priv_nr = 0,
+};
+
+/*! Create new vty structure. */
+//struct prom_vty *vty_create (int vty_sock, void *priv)
+//
+/*! Create new vty structure. */
+// TODO extern struct host prom_host;
+
+/*! Return if this VTY is a shell or not */
+//int prom_vty_shell(struct prom_vty *vty)
+//{
+// return vty->type == VTY_SHELL ? 1 : 0;
+//}
+
+
+/*! close a telnet connection */
+int prom_telnet_close_client(struct osmo_fd *fd)
+{
+ struct prom_connection *conn = (struct prom_connection*)fd->data;
+ char sock_name_buf[OSMO_SOCK_NAME_MAXLEN];
+ int rc;
+
+ /* FIXME: getsockname() always fails: "Bad file descriptor" */
+ rc = osmo_sock_get_name_buf(sock_name_buf, OSMO_SOCK_NAME_MAXLEN, fd->fd);
+ LOGP(DLGLOBAL, LOGL_INFO, "Closing telnet connection %s\n",
+ (rc <= 0) ? "r=NULL<->l=NULL" : sock_name_buf);
+
+ close(fd->fd);
+ osmo_fd_unregister(fd);
+
+ if (conn->dbg) {
+ log_del_target(conn->dbg);
+ talloc_free(conn->dbg);
+ }
+
+ llist_del(&conn->entry);
+ talloc_free(conn);
+ return 0;
+}
+
+
+
+/*! callback from core VTY code about VTY related events */
+void prom_vty_event(enum event event, int sock, struct prom_vty *vty)
+{
+ struct vty_signal_data sig_data;
+ struct prom_connection *connection = vty->priv;
+ struct osmo_fd *bfd;
+
+ //if (vty->type != VTY_TERM)
+ // return;
+
+ sig_data.event = event;
+ sig_data.sock = sock;
+ sig_data.vty = vty;
+ osmo_signal_dispatch(SS_L_VTY, S_VTY_EVENT, &sig_data);
+
+ if (!connection)
+ return;
+
+ bfd = &connection->fd;
+
+ switch (event) {
+ case VTY_READ:
+ bfd->when |= OSMO_FD_READ;
+ break;
+ case VTY_WRITE:
+ bfd->when |= OSMO_FD_WRITE;
+ break;
+ case VTY_CLOSED:
+ /* vty layer is about to free() vty */
+ prom_telnet_close_client(bfd);
+ break;
+ default:
+ break;
+ }
+}
+
+int prom_vty_out_va(struct prom_vty *vty, const char *format, va_list ap)
+{
+ int len = 0;
+ int size = 1024;
+ char buf[1024];
+ char *p = NULL;
+
+ // TODO don't use prom_vty_shell
+ //if (prom_vty_shell(vty)) {
+ // vprintf(format, ap);
+ //} else {
+ va_list args;
+ /* Try to write to initial buffer. */
+ va_copy(args, ap);
+ len = vsnprintf(buf, sizeof buf, format, args);
+ va_end(args);
+
+ /* Initial buffer is not enough. */
+ if (len < 0 || len >= size) {
+ while (1) {
+ if (len > -1)
+ size = len + 1;
+ else
+ size = size * 2;
+
+ p = talloc_realloc_size(vty, p, size);
+ if (!p)
+ return -1;
+
+ va_copy(args, ap);
+ len = vsnprintf(p, size, format, args);
+ va_end(args);
+
+ if (len > -1 && len < size)
+ break;
+ }
+ }
+
+ /* When initial buffer is enough to store all output. */
+ if (!p)
+ p = buf;
+
+ /* Pointer p must point out buffer. */
+ buffer_put(vty->obuf, (unsigned char *) p, len);
+
+ /* If p is not different with buf, it is allocated buffer. */
+ if (p != buf)
+ talloc_free(p);
+ //}
+
+ prom_vty_event(VTY_WRITE, vty->fd, vty);
+
+ return len;
+}
+
+/*! VTY standard output function
+ * \param[in] vty VTY to which we should print
+ * \param[in] format variable-length format string
+ */
+int prom_vty_out(struct prom_vty *vty, const char *format, ...)
+{
+ va_list args;
+ int rc;
+ va_start(args, format);
+ rc = prom_vty_out_va(vty, format, args);
+ va_end(args);
+ return rc;
+}
+
+
+/*! Initialize telnet based VTY interface listening to 127.0.0.1
+ * \param[in] tall_ctx \ref talloc context
+ * \param[in] priv private data to be passed to callback
+ * \param[in] port TCP port number to bind to
+ */
+//int telnet_init(void *tall_ctx, void *priv, int port)
+//{
+// return telnet_init_dynif(tall_ctx, priv, "127.0.0.1", port);
+//}
+
+int osmo_prom_init(void *ctx, int port)
+{
+ return osmo_prom_init_dynif(ctx, "127.0.0.1", port);
+}
+
+/*! Initialize telnet based VTY interface
+ * \param[in] tall_ctx \ref talloc context
+ * \param[in] priv private data to be passed to callback
+ * \param[in] ip IP to listen to ('::1' for localhost, '::0' for all, ...)
+ * \param[in] port TCP port number to bind to
+ */
+//int telnet_init_dynif(void *tall_ctx, void *priv, const char *ip, int port)
+
+int osmo_prom_init_dynif(void *ctx, const char *host, int port)
+{
+ int rc;
+
+ prom_ctx = talloc_named_const(ctx, 1,
+ "prometheus_connection");
+
+ rc = osmo_sock_init_ofd(
+ &server_socket,
+ AF_UNSPEC, SOCK_STREAM, IPPROTO_TCP,
+ host, port, OSMO_SOCK_F_BIND
+ );
+
+ server_socket.data = NULL; // könnte priv
+
+ if (rc < 0) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "Cannot bind telnet at %s %d\n",
+ host, port);
+ return -1;
+ }
+
+ prom_vtyvec = vector_init(VECTOR_MIN_SIZE);
+
+ LOGP(DLGLOBAL, LOGL_NOTICE, "Available via HTTP %s %d\n", host, port);
+ return 0;
+}
+
+/*! Unlock the configuration from a given VTY
+ * \param[in] vty VTY from which the configuration shall be unlocked
+ * \returns 0 in case of success
+ */
+int prom_vty_config_unlock(struct prom_vty *vty)
+{
+ //if (vty_config == 1 && vty->config == 1) {
+ // vty->config = 0;
+ // vty_config = 0;
+ //}
+ return vty->config;
+}
+
+
+void prom_vty_close(struct prom_vty *vty)
+{
+ int i;
+
+ /* VTY_CLOSED is handled by the telnet_interface */
+ prom_vty_event(VTY_CLOSED, vty->fd, vty);
+
+ if (vty->obuf) {
+ /* Flush buffer. */
+ buffer_flush_all(vty->obuf, vty->fd);
+
+ /* Free input buffer. */
+ buffer_free(vty->obuf);
+ vty->obuf = NULL;
+ }
+
+ /* Free command history. */
+ for (i = 0; i < PROM_VTY_MAXHIST; i++)
+ if (vty->hist[i])
+ talloc_free(vty->hist[i]);
+
+ /* Unset vector. */
+ vector_unset(prom_vtyvec, vty->fd);
+
+ /* Close socket (ignore standard I/O streams). */
+ if (vty->fd > 2) {
+ close(vty->fd);
+ vty->fd = -1;
+ }
+
+ if (vty->buf) {
+ talloc_free(vty->buf);
+ vty->buf = NULL;
+ }
+
+ /* Check configure. */
+ prom_vty_config_unlock(vty);
+
+ /* OK free vty. */
+ talloc_free(vty);
+}
+
+//XXX[osmo_counter]static int osmo_counter_handler(struct osmo_counter *counter, void *vty_)
+//XXX[osmo_counter]{
+//XXX[osmo_counter] struct vty *vty = vty_;
+//XXX[osmo_counter] const char *description = counter->description;
+//XXX[osmo_counter]
+//XXX[osmo_counter] if (!counter->description)
+//XXX[osmo_counter] description = counter->name;
+//XXX[osmo_counter]
+//XXX[osmo_counter] //vty_out(vty, " %s%s: %8lu%s",
+//XXX[osmo_counter] // vctx->prefix, description,
+//XXX[osmo_counter] // osmo_counter_get(counter), VTY_NEWLINE);
+//XXX[osmo_counter]
+//XXX[osmo_counter] char buf[1024];
+//XXX[osmo_counter]
+//XXX[osmo_counter] // TODO only export current value and let graphing tools sum it up?
+//XXX[osmo_counter]
+//XXX[osmo_counter] /* TODO ensure name contains only [a-zA-Z0-] */
+//XXX[osmo_counter] const char *name = description;
+//XXX[osmo_counter]
+//XXX[osmo_counter] snprintf(buf, sizeof buf,
+//XXX[osmo_counter] "%s %d\n",
+//XXX[osmo_counter] name,
+//XXX[osmo_counter] osmo_counter_get(counter)
+//XXX[osmo_counter] );
+//XXX[osmo_counter]
+//XXX[osmo_counter] prom_vty_out(vty, "%X\r\n", strlen(buf));
+//XXX[osmo_counter] prom_vty_out(vty, "%s\r\n", buf);
+//XXX[osmo_counter]
+//XXX[osmo_counter] return 0;
+//XXX[osmo_counter]}
+
+
+void sanitize_name(char *name) {
+ for (char c; (c = *name) != '\0'; name++) {
+ if ('a' <= c && c <= 'z') continue;
+ if ('A' <= c && c <= 'Z') continue;
+ if ('0' <= c && c <= '9') continue;
+ if (c == ':' || c == '_') continue;
+ *name = '_';
+ }
+}
+
+
+static int rate_ctr_handler(
+ struct rate_ctr_group *ctrg, struct rate_ctr *ctr,
+ const struct rate_ctr_desc *desc, void *vty_)
+{
+ struct vty *vty = vty_;
+
+ char buf[1024];
+ char name[1024];
+
+ // TODO only export current value and let graphing tools sum it up?
+
+ strcpy(name, desc->name);
+ sanitize_name(name);
+
+ char *group = ctrg->desc->group_name_prefix;
+ //char intv_sec_name[1024];
+ //char intv_min_name[1024];
+ //char intv_hour_name[1024];
+ //char intv_day_name[1024];
+
+ //strcpy(intv_sec_name, name);
+ //strcpy(intv_min_name, name);
+ //strcpy(intv_hour_name, name);
+ //strcpy(intv_day_name, name);
+
+ //strcat(intv_sec_name, "_intv_sec");
+ //strcat(intv_min_name, "_intv_min");
+ //strcat(intv_hour_name, "_intv_hour");
+ //strcat(intv_day_name, "_intv_day");
+
+ snprintf(buf, sizeof buf,
+ //"%s %d\n"
+ //"%s %d\n"
+ //"%s %d\n"
+ //"%s %d\n"
+ "%s{group=\"%s\"} %d\n",
+ //intv_sec_name, ctr->intv[RATE_CTR_INTV_SEC].rate,
+ //intv_min_name, ctr->intv[RATE_CTR_INTV_MIN].rate,
+ //intv_hour_name, ctr->intv[RATE_CTR_INTV_HOUR].rate,
+ //intv_day_name, ctr->intv[RATE_CTR_INTV_DAY].rate
+ name, group, ctr->current
+ );
+
+ prom_vty_out(vty, "%X\r\n", strlen(buf));
+ prom_vty_out(vty, "%s\r\n", buf);
+
+ return 0;
+}
+
+static int rate_ctr_group_handler(struct rate_ctr_group *ctrg, void *vty_)
+{
+ struct vty *vty = vty_;
+
+ //TODO
+// if (ctrg->idx)
+// prom_vty_out(vty, "%s (%d):\r\n",
+// ctrg->desc->group_description, ctrg->idx);
+// else
+// prom_vty_out(vty, "%s:\r\n",
+// ctrg->desc->group_description);
+
+ rate_ctr_for_each_counter(ctrg, rate_ctr_handler, vty);
+
+ return 0;
+}
+
+static int osmo_stat_item_handler(
+ struct osmo_stat_item_group *statg, struct osmo_stat_item *item, void *vty_)
+{
+ struct vty *vty = vty_;
+ const char *unit =
+ item->desc->unit != OSMO_STAT_ITEM_NO_UNIT ?
+ item->desc->unit : "";
+
+ char buf[1024];
+ char name[1024];
+
+ //vty_out(vty, " %s%s: %8" PRIi32 " %s%s",
+ // vctx->prefix, item->desc->description,
+ // osmo_stat_item_get_last(item),
+ // unit, VTY_NEWLINE);
+
+ strcpy(name, item->desc->name);
+ sanitize_name(name);
+
+ char *group = statg->desc->group_name_prefix;
+ int value = osmo_stat_item_get_last(item);
+ // TODO unit
+ snprintf(buf, sizeof buf, "%s{group=\"%s\",unit=\"%s\"} %d\n", name, group, unit, value);
+
+ prom_vty_out(vty, "%X\r\n", strlen(buf));
+ prom_vty_out(vty, "%s\r\n", buf);
+
+ return 0;
+}
+
+static int osmo_stat_item_group_handler(struct osmo_stat_item_group *statg, void *vty_)
+{
+ struct vty *vty = vty_;
+
+ //if (statg->idx)
+ // vty_out(vty, "%s%s (%d):%s", vctx->prefix,
+ // statg->desc->group_description, statg->idx,
+ // VTY_NEWLINE);
+ //else
+ // vty_out(vty, "%s%s:%s", vctx->prefix,
+ // statg->desc->group_description, VTY_NEWLINE);
+
+ osmo_stat_item_for_each_item(statg, osmo_stat_item_handler, vty);
+
+ return 0;
+}
+
+/*! Read data via vty socket. */
+int prom_vty_read(struct prom_vty *vty)
+{
+ //int i;
+ int nbytes;
+ unsigned char buf[VTY_READ_BUFSIZ];
+
+ int vty_sock = vty->fd;
+
+ /* Read raw data from socket */
+ if ((nbytes = read(vty->fd, buf, VTY_READ_BUFSIZ)) <= 0) {
+ if (nbytes < 0) {
+ if (ERRNO_IO_RETRY(errno)) {
+ prom_vty_event(VTY_READ, vty_sock, vty);
+ return 0;
+ }
+ }
+ buffer_reset(vty->obuf);
+ vty->status = PROM_VTY_CLOSE;
+ }
+
+ //LOGP(DLGLOBAL, LOGL_NOTICE, "read %d byte(s)\n", nbytes);
+ prom_vty_out(vty,
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Type: text/plain\r\n"
+ "Transfer-Encoding: chunked\r\n"
+ // "Content-Length: 6\r\n"
+ "\r\n"
+ );
+ //vty->status = PROM_VTY_CLOSE;
+
+ //XXX[osmo_counter]osmo_counters_for_each(osmo_counter_handler, vty);
+ rate_ctr_for_each_group(rate_ctr_group_handler, vty);
+ osmo_stat_item_for_each_group(osmo_stat_item_group_handler, vty);
+
+ /* finish chunked transfer encoding */
+ prom_vty_out(vty, "0\r\n\r\n");
+
+ // TODO parse
+
+ /* Check status. */
+ if (vty->status == PROM_VTY_CLOSE) {
+ prom_vty_close(vty);
+ return -EBADF;
+ } else {
+ prom_vty_event(VTY_WRITE, vty_sock, vty);
+ prom_vty_event(VTY_READ, vty_sock, vty);
+ }
+
+ return 0;
+}
+
+
+static int client_data(struct osmo_fd *fd, unsigned int what)
+{
+ struct prom_connection *conn = fd->data;
+ int rc = 0;
+
+ if (what & OSMO_FD_READ) {
+ conn->fd.when &= ~OSMO_FD_READ;
+ // TODO feed server
+ rc = prom_vty_read(conn->vty);
+ }
+
+ /* vty might have been closed from vithin prom_vty_read() */
+ if (rc == -EBADF)
+ return rc;
+
+ if (what & OSMO_FD_WRITE) {
+ rc = buffer_flush_all(conn->vty->obuf, fd->fd);
+ if (rc == BUFFER_EMPTY)
+ conn->fd.when &= ~OSMO_FD_WRITE;
+ }
+
+ return rc;
+}
+
+
+/*! Allocate a new vty interface structure */
+struct prom_vty *prom_vty_new(void)
+{
+ struct prom_vty *new = talloc_zero(tall_vty_ctx, struct vty);
+
+ if (!new)
+ goto out;
+
+ INIT_LLIST_HEAD(&new->parent_nodes);
+
+ new->obuf = buffer_new(new, 0); /* Use default buffer size. */
+ if (!new->obuf)
+ goto out_new;
+ new->buf = _talloc_zero(new, PROM_HTTP_BUFSIZ, "prom_vty_new->buf");
+ if (!new->buf)
+ goto out_obuf;
+
+ new->max = PROM_HTTP_BUFSIZ;
+ new->fd = -1;
+
+ return new;
+
+out_obuf:
+ buffer_free(new->obuf);
+out_new:
+ talloc_free(new);
+ new = NULL;
+out:
+ return new;
+}
+
+struct vty *
+prom_vty_create (int vty_sock, void *priv)
+{
+ struct prom_vty *vty;
+
+ struct termios t = {};
+
+ tcgetattr(vty_sock, &t);
+ cfmakeraw(&t);
+ tcsetattr(vty_sock, TCSANOW, &t);
+
+ /* Allocate new vty structure and set up default values. */
+ vty = prom_vty_new ();
+ vty->fd = vty_sock;
+ vty->priv = priv;
+ //vty->type = VTY_TERM; // TODO kill vty->type
+ //if (!password_check)
+ // {
+ // if (prom_host.advanced)
+ // vty->node = ENABLE_NODE;
+ // else
+ // vty->node = VIEW_NODE;
+ // }
+ //else
+ // vty->node = AUTH_NODE;
+ vty->fail = 0;
+ vty->cp = 0;
+ // TODO vty_clear_buf (vty);
+ vty->length = 0;
+ memset (vty->hist, 0, sizeof (vty->hist));
+ vty->hp = 0;
+ vty->hindex = 0;
+ vector_set_index (prom_vtyvec, vty_sock, vty);
+ vty->status = PROM_VTY_NORMAL;
+ //if (prom_host.lines >= 0)
+ // vty->lines = prom_host.lines;
+ //else
+ // vty->lines = -1;
+
+ //if (password_check)
+ // {
+ // /* Vty is not available if password isn't set. */
+ // if (prom_host.password == NULL && prom_host.password_encrypt == NULL)
+ // {
+ // prom_vty_out (vty, "Vty password is not set.%s", VTY_NEWLINE);
+ // vty->status = PROM_VTY_CLOSE;
+ // prom_vty_close (vty);
+ // return NULL;
+ // }
+ // }
+
+ /* Say hello to the world. */
+ //prom_vty_hello (vty);
+ //if (password_check)
+ // prom_vty_out (vty, "%sUser Access Verification%s%s", VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE);
+
+ /* Setting up terminal. */
+ // TODO vty_will_echo (vty);
+ //TODO vty_will_suppress_go_ahead (vty);
+
+ // TODO vty_dont_linemode (vty);
+ // TODO vty_do_window_size (vty);
+ /* vty_dont_lflow_ahead (vty); */
+
+ // TODO vty_prompt (vty);
+
+ /* Add read/write thread. */
+ prom_vty_event (VTY_WRITE, vty_sock, vty);
+ prom_vty_event (VTY_READ, vty_sock, vty);
+
+ return vty;
+}
+
+
+static int prom_new_connection(struct osmo_fd *fd, unsigned int what)
+{
+ //LOGP(DLGLOBAL, LOGL_ERROR, "derp\n");
+ struct prom_connection *connection;
+ struct sockaddr_in sockaddr;
+ socklen_t len = sizeof(sockaddr);
+ int new_connection = accept(fd->fd, (struct sockaddr*)&sockaddr, &len);
+ char sock_name_buf[OSMO_SOCK_NAME_MAXLEN];
+ int rc;
+
+ if (new_connection < 0) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "HTTP accept failed\n");
+ return new_connection;
+ }
+
+ rc = osmo_sock_get_name_buf(sock_name_buf, OSMO_SOCK_NAME_MAXLEN, new_connection);
+ LOGP(DLGLOBAL, LOGL_INFO, "Accept()ed new HTTP connection %s\n",
+ (rc <= 0) ? "r=NULL<->l=NULL" : sock_name_buf);
+
+ connection = talloc_zero(prom_ctx, struct prom_connection);
+ connection->priv = fd->data;
+ connection->fd.data = connection;
+ connection->fd.fd = new_connection;
+ connection->fd.when = OSMO_FD_READ;
+ connection->fd.cb = client_data;
+ rc = osmo_fd_register(&connection->fd);
+ if (rc < 0) {
+ talloc_free(connection);
+ return rc;
+ }
+ llist_add_tail(&connection->entry, &prom_active_connections);
+
+ // TODO create server
+ connection->vty = prom_vty_create(new_connection, connection);
+ if (!connection->vty) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "couldn't create VTY\n");
+ /* prom_vty_create() is already closing the fd if it returns NULL */
+ talloc_free(connection);
+ return -1;
+ }
+
+ return 0;
+}
+