mirror of https://gerrit.osmocom.org/osmo-sysmon
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
This commit is contained in:
parent
5d42b8ec98
commit
9a852f2dd8
|
@ -42,6 +42,7 @@ PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 0.11.0)
|
|||
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)
|
||||
|
|
|
@ -25,6 +25,9 @@ verify_value_string_arrays_are_terminated.py $(find . -name "*.[hc]")
|
|||
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
|
||||
|
|
|
@ -9,6 +9,7 @@ AM_CFLAGS = \
|
|||
-Wall \
|
||||
$(LIBOSMOCORE_CFLAGS) \
|
||||
$(LIBOSMOGSM_CFLAGS) \
|
||||
$(LIBOSMONETIF_CFLAGS) \
|
||||
$(NULL)
|
||||
|
||||
AM_LDFLAGS = \
|
||||
|
@ -22,12 +23,13 @@ bin_PROGRAMS = \
|
|||
|
||||
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 @@ osmo_sysmon_SOURCES = \
|
|||
osysmon_rtnl.c \
|
||||
osysmon_file.c \
|
||||
osysmon_ping.c \
|
||||
osysmon_openvpn.c \
|
||||
osysmon_main.c \
|
||||
$(NULL)
|
||||
|
||||
|
|
22
src/client.c
22
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 @@ char *make_authority(void *ctx, const struct host_cfg *cfg)
|
|||
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -23,3 +23,8 @@ struct host_cfg {
|
|||
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);
|
||||
|
|
|
@ -15,6 +15,8 @@ struct osysmon_state {
|
|||
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 @@ enum osysmon_vty_node {
|
|||
CTRL_CLIENT_NODE = _LAST_OSMOVTY_NODE + 1,
|
||||
CTRL_CLIENT_GETVAR_NODE,
|
||||
NETDEV_NODE,
|
||||
OPENVPN_NODE,
|
||||
PING_NODE,
|
||||
};
|
||||
|
||||
|
@ -48,5 +51,8 @@ int osysmon_sysinfo_poll(struct value_node *parent);
|
|||
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);
|
||||
|
|
|
@ -199,6 +199,7 @@ int main(int argc, char **argv)
|
|||
|
||||
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 @@ int main(int argc, char **argv)
|
|||
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 @@ int main(int argc, char **argv)
|
|||
|
||||
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 @@ int main(int argc, char **argv)
|
|||
|
||||
display_update(root);
|
||||
value_node_del(root);
|
||||
|
||||
if (vpns)
|
||||
osmo_select_main(0);
|
||||
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue