Change in osmo-sysmon[master]: Add OpenVPN probe

This is merely a historical archive of years 2008-2021, before the migration to mailman3.

A maintained and still updated list archive can be found at https://lists.osmocom.org/hyperkitty/list/gerrit-log@lists.osmocom.org/.

Max gerrit-no-reply at lists.osmocom.org
Thu Feb 21 09:01:13 UTC 2019


Max has submitted this change and it was merged. ( https://gerrit.osmocom.org/12763 )

Change subject: Add OpenVPN probe
......................................................................

Add OpenVPN probe

This adds support for OpenVPN status probe which uses OpenVPN's
management interface (configured via 'management 127.0.0.1 1234' in
OpenVPN's config).

The output looks as follows:
...
  OpenVPN
    127.0.0.1:1234
      status: CONNECTED
      tunnel: 10.8.0.15
      remote: 144.76.43.77:1194
    localhost:4242
      status: management interface incompatible
    127.0.0.1:4444
      status: management interface unavailable
...

We show tunnel's IP (if available) as well as remote (OpenVPN server
itself) address/port in addition to general connection status. If
management interface is unavailable it's reported as such. If we've
managed to establish connection with a given management interface but
are unable to obtain expected information than we report this
incompatibility as well.

Related: SYS#2655
Change-Id: I4493e19b9a09dcebd289457eacd1719f7f8cc31c
---
M configure.ac
M contrib/jenkins.sh
M src/Makefile.am
M src/client.c
M src/client.h
M src/osysmon.h
M src/osysmon_main.c
A src/osysmon_openvpn.c
8 files changed, 342 insertions(+), 1 deletion(-)

Approvals:
  Jenkins Builder: Verified
  Harald Welte: Looks good to me, approved

Objections:
  Pau Espin Pedrol: I would prefer this is not merged as is



diff --git a/configure.ac b/configure.ac
index d98de89..44a040c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -42,6 +42,7 @@
 PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 0.11.0)
 PKG_CHECK_MODULES(LIBOSMOCTRL, libosmoctrl >= 0.11.0)
 PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 0.11.0)
+PKG_CHECK_MODULES(LIBOSMONETIF, libosmo-netif >= 0.4.0)
 PKG_CHECK_MODULES(LIBMNL, libmnl)
 dnl FIXME: bump to 1.10.0 once it's available on build slaves and remove workaround from osysmon_ping.c
 PKG_CHECK_MODULES(LIBOPING, liboping >= 1.9.0)
diff --git a/contrib/jenkins.sh b/contrib/jenkins.sh
index b1529c4..631f95a 100755
--- a/contrib/jenkins.sh
+++ b/contrib/jenkins.sh
@@ -25,6 +25,9 @@
 export PKG_CONFIG_PATH="$inst/lib/pkgconfig:$PKG_CONFIG_PATH"
 export LD_LIBRARY_PATH="$inst/lib"
 
+osmo-build-dep.sh libosmo-abis
+osmo-build-dep.sh libosmo-netif "" '--disable-doxygen'
+
 set +x
 echo
 echo
diff --git a/src/Makefile.am b/src/Makefile.am
index f639023..f9b79f2 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -9,6 +9,7 @@
 	-Wall \
 	$(LIBOSMOCORE_CFLAGS) \
 	$(LIBOSMOGSM_CFLAGS) \
+	$(LIBOSMONETIF_CFLAGS) \
 	$(NULL)
 
 AM_LDFLAGS = \
@@ -22,12 +23,13 @@
 
 noinst_LTLIBRARIES = libintern.la
 libintern_la_SOURCES = simple_ctrl.c client.c
-libintern_la_LIBADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS)
+libintern_la_LIBADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMONETIF_LIBS)
 
 osmo_sysmon_CFLAGS = $(LIBMNL_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOPING_CFLAGS) $(AM_CFLAGS)
 
 osmo_sysmon_LDADD = $(LDADD) \
 	$(LIBOSMOVTY_LIBS) \
+	$(LIBOSMONETIF_LIBS) \
 	$(LIBMNL_LIBS) \
 	$(LIBOPING_LIBS) \
 	$(NULL)
@@ -39,6 +41,7 @@
 	osysmon_rtnl.c \
 	osysmon_file.c \
 	osysmon_ping.c \
+	osysmon_openvpn.c \
 	osysmon_main.c \
 	$(NULL)
 
diff --git a/src/client.c b/src/client.c
index 6b37fc6..758884d 100644
--- a/src/client.c
+++ b/src/client.c
@@ -27,6 +27,7 @@
 #include <talloc.h>
 
 #include <osmocom/core/utils.h>
+#include <osmocom/netif/stream.h>
 
 #include "client.h"
 
@@ -71,3 +72,24 @@
 
 	return talloc_asprintf(ctx, "%s:%u", cfg->remote_host, cfg->remote_port);
 }
+
+struct osmo_stream_cli *make_tcp_client(struct host_cfg *cfg)
+{
+	struct osmo_stream_cli *cl = osmo_stream_cli_create(cfg);
+	if (cl) {
+		osmo_stream_cli_set_addr(cl, cfg->remote_host);
+		osmo_stream_cli_set_port(cl, cfg->remote_port);
+	}
+
+	return cl;
+}
+
+void update_name(struct host_cfg *cfg, const char *new_name)
+{
+	osmo_talloc_replace_string(cfg, (char **)&cfg->name, new_name);
+}
+
+void update_host(struct host_cfg *cfg, const char *new_host)
+{
+	osmo_talloc_replace_string(cfg, (char **)&cfg->remote_host, new_host);
+}
diff --git a/src/client.h b/src/client.h
index 605ddd7..d878450 100644
--- a/src/client.h
+++ b/src/client.h
@@ -23,3 +23,8 @@
 struct host_cfg *host_cfg_alloc(void *ctx, const char *name, const char *host, uint16_t port);
 bool match_config(const struct host_cfg *cfg, const char *match, enum match_kind k);
 char *make_authority(void *ctx, const struct host_cfg *cfg);
+
+struct osmo_stream_cli *make_tcp_client(struct host_cfg *cfg);
+
+void update_name(struct host_cfg *cfg, const char *new_name);
+void update_host(struct host_cfg *cfg, const char *new_host);
diff --git a/src/osysmon.h b/src/osysmon.h
index df8bf8d..2f82c47 100644
--- a/src/osysmon.h
+++ b/src/osysmon.h
@@ -15,6 +15,8 @@
 	struct rtnl_client_state *rcs;
 	/* list of 'struct ctrl client' */
 	struct llist_head ctrl_clients;
+	/* list of 'struct openvpn_client' */
+	struct llist_head openvpn_clients;
 	/* list of 'struct netdev' */
 	struct llist_head netdevs;
 	/* list of 'struct osysmon_file' */
@@ -30,6 +32,7 @@
 	CTRL_CLIENT_NODE = _LAST_OSMOVTY_NODE + 1,
 	CTRL_CLIENT_GETVAR_NODE,
 	NETDEV_NODE,
+	OPENVPN_NODE,
 	PING_NODE,
 };
 
@@ -48,5 +51,8 @@
 int osysmon_ping_init();
 int osysmon_ping_poll(struct value_node *parent);
 
+int osysmon_openvpn_init();
+int osysmon_openvpn_poll(struct value_node *parent);
+
 int osysmon_file_init();
 int osysmon_file_poll(struct value_node *parent);
diff --git a/src/osysmon_main.c b/src/osysmon_main.c
index 91d5039..18f6299 100644
--- a/src/osysmon_main.c
+++ b/src/osysmon_main.c
@@ -199,6 +199,7 @@
 
 	g_oss = talloc_zero(NULL, struct osysmon_state);
 	INIT_LLIST_HEAD(&g_oss->ctrl_clients);
+	INIT_LLIST_HEAD(&g_oss->openvpn_clients);
 	INIT_LLIST_HEAD(&g_oss->netdevs);
 	INIT_LLIST_HEAD(&g_oss->files);
 
@@ -206,6 +207,7 @@
 	handle_options(argc, argv);
 	osysmon_sysinfo_init();
 	osysmon_ctrl_init();
+	osysmon_openvpn_init();
 	osysmon_rtnl_init();
 	ping_init = osysmon_ping_init();
 	osysmon_file_init();
@@ -231,6 +233,7 @@
 
 	while (1) {
 		struct value_node *root = value_node_add(NULL, "root", NULL);
+		int vpns = osysmon_openvpn_poll(root);
 		osysmon_sysinfo_poll(root);
 		osysmon_ctrl_poll(root);
 		osysmon_rtnl_poll(root);
@@ -242,6 +245,10 @@
 
 		display_update(root);
 		value_node_del(root);
+
+		if (vpns)
+			osmo_select_main(0);
+
 		sleep(1);
 	}
 
diff --git a/src/osysmon_openvpn.c b/src/osysmon_openvpn.c
new file mode 100644
index 0000000..135a532
--- /dev/null
+++ b/src/osysmon_openvpn.c
@@ -0,0 +1,294 @@
+/* Simple Osmocom System Monitor (osysmon): Support for OpenVPN monitoring */
+
+/* (C) 2019 by sysmocom - s.f.m.c. GmbH.
+ * Author: Max Suraev
+ * 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.
+ *
+ *  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 <string.h>
+#include <stdbool.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/netif/stream.h>
+
+#include "osysmon.h"
+#include "client.h"
+#include "value_node.h"
+
+/***********************************************************************
+ * Data model
+ ***********************************************************************/
+
+#define OVPN_LOG(ctx, vpn, fmt, args...)				\
+	fprintf(stderr, "OpenVPN [%s]: " fmt, make_authority(ctx, vpn->cfg), ##args)
+
+/* max number of csv in response */
+#define MAX_RESP_COMPONENTS 6
+
+/* a single OpenVPN management interface client */
+struct openvpn_client {
+	/* links to osysmon.openvpn_clients */
+	struct llist_head list;
+	struct host_cfg *cfg;
+	struct osmo_stream_cli *mgmt;
+	/* fields below are parsed from response to 'state' command on mgmt interface */
+	struct host_cfg *rem_cfg;
+	char *tun_ip;
+	bool connected;
+};
+
+static char *parse_state(struct msgb *msg, struct openvpn_client *vpn)
+{
+	char tmp[128];
+	char *tok;
+	unsigned int i = 0;
+	uint8_t *m = msgb_data(msg);
+
+	if (msgb_length(msg) > 128)
+		OVPN_LOG(msg, vpn, "received message too long (%d > %u), truncating...\n", msgb_length(msg), 128);
+
+	if (msgb_length(msg) > 0) {
+		if (!isdigit(m[0])) /* skip OpenVPN greetings and alike */
+			return NULL;
+	} else {
+		OVPN_LOG(msg, vpn, "received message is empty, ignoring...\n");
+		return NULL;
+	}
+
+	OSMO_STRLCPY_ARRAY(tmp, (char *)m);
+
+	for (tok = strtok(tmp, ","); tok && i < MAX_RESP_COMPONENTS; tok = strtok(NULL, ",")) {
+		/* The string format is documented in https://openvpn.net/community-resources/management-interface/ */
+		if (tok) { /* Parse csv string and pick interesting tokens while ignoring the rest. */
+			switch (i++) {
+			case 1:
+				update_name(vpn->rem_cfg, tok);
+				break;
+			case 3:
+				osmo_talloc_replace_string(vpn->rem_cfg, &vpn->tun_ip, tok);
+				break;
+			case 4:
+				update_host(vpn->rem_cfg, tok);
+				break;
+			case 5:
+				vpn->rem_cfg->remote_port = atoi(tok);
+				break;
+			}
+		}
+	}
+	return NULL;
+}
+
+static struct openvpn_client *openvpn_client_find_or_make(const struct osysmon_state *os,
+							  const char *host, uint16_t port)
+{
+	struct openvpn_client *vpn;
+	llist_for_each_entry(vpn, &os->openvpn_clients, list) {
+		if (match_config(vpn->cfg, host, MATCH_HOST) && vpn->cfg->remote_port == port)
+			return vpn;
+	}
+
+	return NULL;
+}
+
+static int connect_cb(struct osmo_stream_cli *conn)
+{
+	struct openvpn_client *vpn = osmo_stream_cli_get_data(conn);
+
+	update_name(vpn->rem_cfg, "management interface incompatible");
+	vpn->connected = true; /* FIXME: there's no callback for lost connection to drop this flag */
+
+	return 0;
+}
+
+static int read_cb(struct osmo_stream_cli *conn)
+{
+	int bytes;
+	struct openvpn_client *vpn = osmo_stream_cli_get_data(conn);
+	struct msgb *msg = msgb_alloc(1024, "OpenVPN");
+	if (!msg) {
+		OVPN_LOG(conn, vpn, "unable to allocate message in callback\n");
+		return 0;
+	}
+
+	bytes = osmo_stream_cli_recv(conn, msg);
+	if (bytes < 0)
+		OVPN_LOG(msg, vpn, "unable to receive message in callback\n");
+	else
+		parse_state(msg, vpn);
+
+	msgb_free(msg);
+
+	return 0;
+}
+
+static bool openvpn_client_create(struct osysmon_state *os, const char *name, const char *host, uint16_t port)
+{
+	struct openvpn_client *vpn = openvpn_client_find_or_make(os, host, port);
+	if (vpn)
+		return true;
+
+	vpn = talloc_zero(os, struct openvpn_client);
+	if (!vpn)
+		return false;
+
+	vpn->connected = false;
+
+	vpn->cfg = host_cfg_alloc(vpn, name, host, port);
+	if (!vpn->cfg)
+		goto dealloc;
+
+	vpn->rem_cfg = host_cfg_alloc(vpn, "management interface unavailable", NULL, 0);
+	if (!vpn->rem_cfg)
+		goto dealloc;
+
+	vpn->mgmt = make_tcp_client(vpn->cfg);
+	if (!vpn->mgmt)	{
+		OVPN_LOG(vpn->rem_cfg, vpn, "failed to create TCP client\n");
+		goto dealloc;
+	}
+
+	/* Wait for 1 minute before attempting to reconnect to management interface */
+	osmo_stream_cli_set_reconnect_timeout(vpn->mgmt, 60);
+	osmo_stream_cli_set_read_cb(vpn->mgmt, read_cb);
+	osmo_stream_cli_set_connect_cb(vpn->mgmt, connect_cb);
+
+	if (osmo_stream_cli_open(vpn->mgmt) < 0) {
+		OVPN_LOG(vpn->rem_cfg, vpn, "failed to connect to management interface\n");
+		goto dealloc;
+	}
+
+	osmo_stream_cli_set_data(vpn->mgmt, vpn);
+	llist_add_tail(&vpn->list, &os->openvpn_clients);
+
+	return true;
+
+dealloc:
+	talloc_free(vpn);
+	return false;
+}
+
+static void openvpn_client_destroy(struct openvpn_client *vpn)
+{
+	if (!vpn)
+		return;
+
+	osmo_stream_cli_destroy(vpn->mgmt);
+	llist_del(&vpn->list);
+	talloc_free(vpn);
+}
+
+
+/***********************************************************************
+ * VTY
+ ***********************************************************************/
+
+#define OPENVPN_STR "Configure OpenVPN management interface address\n"
+
+DEFUN(cfg_openvpn, cfg_openvpn_cmd,
+      "openvpn HOST <1-65535>",
+      OPENVPN_STR "Name of the host to connect to\n" "Management interface port\n")
+{
+	uint16_t port = atoi(argv[1]);
+
+	if (!openvpn_client_create(g_oss, "OpenVPN", argv[0], port)) {
+		vty_out(vty, "Failed to create OpenVPN client for %s:%u%s", argv[0], port, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_openvpn, cfg_no_openvpn_cmd,
+      "no openvpn HOST <1-65535>",
+      NO_STR OPENVPN_STR "Name of the host to connect to\n" "Management interface port\n")
+{
+	uint16_t port = atoi(argv[1]);
+	struct openvpn_client *vpn = openvpn_client_find_or_make(g_oss, argv[0], port);
+	if (!vpn) {
+		vty_out(vty, "OpenVPN client %s:%u doesn't exist%s", argv[0], port, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	openvpn_client_destroy(vpn);
+
+	return CMD_SUCCESS;
+}
+
+
+/***********************************************************************
+ * Runtime Code
+ ***********************************************************************/
+
+static int openvpn_client_poll(struct openvpn_client *vpn, struct value_node *parent)
+{
+	char *remote = make_authority(parent, vpn->rem_cfg);
+	struct value_node *vn_host = value_node_find_or_add(parent, make_authority(parent, vpn->cfg));
+	struct msgb *msg = msgb_alloc(128, "state");
+	if (!msg) {
+		value_node_add(vn_host, "msgb", "memory allocation failure");
+		return 0;
+	}
+
+	if (vpn->rem_cfg->name)
+		value_node_add(vn_host, "status", vpn->rem_cfg->name);
+
+	if (vpn->tun_ip)
+		value_node_add(vn_host, "tunnel", vpn->tun_ip);
+
+	if (remote)
+		value_node_add(vn_host, "remote", remote);
+
+	/* FIXME: there's no way to check client state so this might be triggered even while it's reconnecting */
+	if (vpn->connected) { /* re-trigger state command */
+		msgb_printf(msg, "state\n");
+		osmo_stream_cli_send(vpn->mgmt, msg);
+	}
+
+	return 0;
+}
+
+/* called once on startup before config file parsing */
+int osysmon_openvpn_init()
+{
+	install_element(CONFIG_NODE, &cfg_openvpn_cmd);
+	install_element(CONFIG_NODE, &cfg_no_openvpn_cmd);
+
+	return 0;
+}
+
+/* called periodically */
+int osysmon_openvpn_poll(struct value_node *parent)
+{
+	int num_vpns = llist_count(&g_oss->openvpn_clients);
+	if (num_vpns) {
+		struct value_node *vn_vpn = value_node_add(parent, "OpenVPN", NULL);
+		struct openvpn_client *vpn;
+		llist_for_each_entry(vpn, &g_oss->openvpn_clients, list)
+			openvpn_client_poll(vpn, vn_vpn);
+	}
+
+	return num_vpns;
+}

-- 
To view, visit https://gerrit.osmocom.org/12763
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings

Gerrit-Project: osmo-sysmon
Gerrit-Branch: master
Gerrit-MessageType: merged
Gerrit-Change-Id: I4493e19b9a09dcebd289457eacd1719f7f8cc31c
Gerrit-Change-Number: 12763
Gerrit-PatchSet: 7
Gerrit-Owner: Max <msuraev at sysmocom.de>
Gerrit-Reviewer: Harald Welte <laforge at gnumonks.org>
Gerrit-Reviewer: Jenkins Builder (1000002)
Gerrit-Reviewer: Max <msuraev at sysmocom.de>
Gerrit-Reviewer: Pau Espin Pedrol <pespin at sysmocom.de>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.osmocom.org/pipermail/gerrit-log/attachments/20190221/c3bc8710/attachment.htm>


More information about the gerrit-log mailing list