blob: 148e3b810eb29b7e60d73afd37a3f5fc2f6dafa8 [file] [log] [blame]
Max9a852f22019-01-31 15:58:57 +01001/* Simple Osmocom System Monitor (osysmon): Support for OpenVPN monitoring */
2
3/* (C) 2019 by sysmocom - s.f.m.c. GmbH.
4 * Author: Max Suraev
5 * All Rights Reserved.
6 *
7 * SPDX-License-Identifier: GPL-2.0+
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
22 * MA 02110-1301, USA.
23 */
24
25#include <string.h>
26#include <stdbool.h>
27#include <ctype.h>
28#include <errno.h>
29
30#include <osmocom/core/utils.h>
31#include <osmocom/vty/vty.h>
32#include <osmocom/vty/command.h>
33#include <osmocom/core/msgb.h>
34#include <osmocom/netif/stream.h>
35
36#include "osysmon.h"
37#include "client.h"
38#include "value_node.h"
39
40/***********************************************************************
41 * Data model
42 ***********************************************************************/
43
44#define OVPN_LOG(ctx, vpn, fmt, args...) \
45 fprintf(stderr, "OpenVPN [%s]: " fmt, make_authority(ctx, vpn->cfg), ##args)
46
47/* max number of csv in response */
48#define MAX_RESP_COMPONENTS 6
49
50/* a single OpenVPN management interface client */
51struct openvpn_client {
52 /* links to osysmon.openvpn_clients */
53 struct llist_head list;
54 struct host_cfg *cfg;
55 struct osmo_stream_cli *mgmt;
56 /* fields below are parsed from response to 'state' command on mgmt interface */
57 struct host_cfg *rem_cfg;
58 char *tun_ip;
59 bool connected;
60};
61
62static char *parse_state(struct msgb *msg, struct openvpn_client *vpn)
63{
64 char tmp[128];
65 char *tok;
66 unsigned int i = 0;
67 uint8_t *m = msgb_data(msg);
68
69 if (msgb_length(msg) > 128)
70 OVPN_LOG(msg, vpn, "received message too long (%d > %u), truncating...\n", msgb_length(msg), 128);
71
72 if (msgb_length(msg) > 0) {
73 if (!isdigit(m[0])) /* skip OpenVPN greetings and alike */
74 return NULL;
75 } else {
76 OVPN_LOG(msg, vpn, "received message is empty, ignoring...\n");
77 return NULL;
78 }
79
80 OSMO_STRLCPY_ARRAY(tmp, (char *)m);
81
82 for (tok = strtok(tmp, ","); tok && i < MAX_RESP_COMPONENTS; tok = strtok(NULL, ",")) {
83 /* The string format is documented in https://openvpn.net/community-resources/management-interface/ */
84 if (tok) { /* Parse csv string and pick interesting tokens while ignoring the rest. */
85 switch (i++) {
86 case 1:
87 update_name(vpn->rem_cfg, tok);
88 break;
89 case 3:
90 osmo_talloc_replace_string(vpn->rem_cfg, &vpn->tun_ip, tok);
91 break;
92 case 4:
93 update_host(vpn->rem_cfg, tok);
94 break;
95 case 5:
96 vpn->rem_cfg->remote_port = atoi(tok);
97 break;
98 }
99 }
100 }
101 return NULL;
102}
103
104static struct openvpn_client *openvpn_client_find_or_make(const struct osysmon_state *os,
105 const char *host, uint16_t port)
106{
107 struct openvpn_client *vpn;
108 llist_for_each_entry(vpn, &os->openvpn_clients, list) {
109 if (match_config(vpn->cfg, host, MATCH_HOST) && vpn->cfg->remote_port == port)
110 return vpn;
111 }
112
113 return NULL;
114}
115
Max6cbdcaf2019-02-21 10:06:19 +0100116static int disconnect_cb(struct osmo_stream_cli *conn)
117{
118 struct openvpn_client *vpn = osmo_stream_cli_get_data(conn);
119
120 update_name(vpn->rem_cfg, "management interface unavailable");
121 vpn->connected = false;
122 talloc_free(vpn->tun_ip);
123 talloc_free(vpn->rem_cfg->remote_host);
124 vpn->tun_ip = NULL;
125 vpn->rem_cfg->remote_host = NULL;
126
127 return 0;
128}
129
Max9a852f22019-01-31 15:58:57 +0100130static int connect_cb(struct osmo_stream_cli *conn)
131{
132 struct openvpn_client *vpn = osmo_stream_cli_get_data(conn);
133
134 update_name(vpn->rem_cfg, "management interface incompatible");
Max6cbdcaf2019-02-21 10:06:19 +0100135 vpn->connected = true;
Max9a852f22019-01-31 15:58:57 +0100136
137 return 0;
138}
139
140static int read_cb(struct osmo_stream_cli *conn)
141{
142 int bytes;
143 struct openvpn_client *vpn = osmo_stream_cli_get_data(conn);
144 struct msgb *msg = msgb_alloc(1024, "OpenVPN");
145 if (!msg) {
146 OVPN_LOG(conn, vpn, "unable to allocate message in callback\n");
147 return 0;
148 }
149
150 bytes = osmo_stream_cli_recv(conn, msg);
151 if (bytes < 0)
152 OVPN_LOG(msg, vpn, "unable to receive message in callback\n");
153 else
154 parse_state(msg, vpn);
155
156 msgb_free(msg);
157
158 return 0;
159}
160
161static bool openvpn_client_create(struct osysmon_state *os, const char *name, const char *host, uint16_t port)
162{
163 struct openvpn_client *vpn = openvpn_client_find_or_make(os, host, port);
164 if (vpn)
165 return true;
166
167 vpn = talloc_zero(os, struct openvpn_client);
168 if (!vpn)
169 return false;
170
171 vpn->connected = false;
172
173 vpn->cfg = host_cfg_alloc(vpn, name, host, port);
174 if (!vpn->cfg)
175 goto dealloc;
176
177 vpn->rem_cfg = host_cfg_alloc(vpn, "management interface unavailable", NULL, 0);
178 if (!vpn->rem_cfg)
179 goto dealloc;
180
181 vpn->mgmt = make_tcp_client(vpn->cfg);
182 if (!vpn->mgmt) {
183 OVPN_LOG(vpn->rem_cfg, vpn, "failed to create TCP client\n");
184 goto dealloc;
185 }
186
187 /* Wait for 1 minute before attempting to reconnect to management interface */
188 osmo_stream_cli_set_reconnect_timeout(vpn->mgmt, 60);
189 osmo_stream_cli_set_read_cb(vpn->mgmt, read_cb);
190 osmo_stream_cli_set_connect_cb(vpn->mgmt, connect_cb);
Max6cbdcaf2019-02-21 10:06:19 +0100191 osmo_stream_cli_set_disconnect_cb(vpn->mgmt, disconnect_cb);
Max9a852f22019-01-31 15:58:57 +0100192
193 if (osmo_stream_cli_open(vpn->mgmt) < 0) {
194 OVPN_LOG(vpn->rem_cfg, vpn, "failed to connect to management interface\n");
195 goto dealloc;
196 }
197
198 osmo_stream_cli_set_data(vpn->mgmt, vpn);
199 llist_add_tail(&vpn->list, &os->openvpn_clients);
200
201 return true;
202
203dealloc:
204 talloc_free(vpn);
205 return false;
206}
207
208static void openvpn_client_destroy(struct openvpn_client *vpn)
209{
210 if (!vpn)
211 return;
212
213 osmo_stream_cli_destroy(vpn->mgmt);
214 llist_del(&vpn->list);
215 talloc_free(vpn);
216}
217
218
219/***********************************************************************
220 * VTY
221 ***********************************************************************/
222
223#define OPENVPN_STR "Configure OpenVPN management interface address\n"
224
225DEFUN(cfg_openvpn, cfg_openvpn_cmd,
226 "openvpn HOST <1-65535>",
227 OPENVPN_STR "Name of the host to connect to\n" "Management interface port\n")
228{
229 uint16_t port = atoi(argv[1]);
230
231 if (!openvpn_client_create(g_oss, "OpenVPN", argv[0], port)) {
232 vty_out(vty, "Failed to create OpenVPN client for %s:%u%s", argv[0], port, VTY_NEWLINE);
233 return CMD_WARNING;
234 }
235
236 return CMD_SUCCESS;
237}
238
239DEFUN(cfg_no_openvpn, cfg_no_openvpn_cmd,
240 "no openvpn HOST <1-65535>",
241 NO_STR OPENVPN_STR "Name of the host to connect to\n" "Management interface port\n")
242{
243 uint16_t port = atoi(argv[1]);
244 struct openvpn_client *vpn = openvpn_client_find_or_make(g_oss, argv[0], port);
245 if (!vpn) {
246 vty_out(vty, "OpenVPN client %s:%u doesn't exist%s", argv[0], port, VTY_NEWLINE);
247 return CMD_WARNING;
248 }
249
250 openvpn_client_destroy(vpn);
251
252 return CMD_SUCCESS;
253}
254
255
256/***********************************************************************
257 * Runtime Code
258 ***********************************************************************/
259
260static int openvpn_client_poll(struct openvpn_client *vpn, struct value_node *parent)
261{
262 char *remote = make_authority(parent, vpn->rem_cfg);
263 struct value_node *vn_host = value_node_find_or_add(parent, make_authority(parent, vpn->cfg));
264 struct msgb *msg = msgb_alloc(128, "state");
265 if (!msg) {
266 value_node_add(vn_host, "msgb", "memory allocation failure");
267 return 0;
268 }
269
270 if (vpn->rem_cfg->name)
271 value_node_add(vn_host, "status", vpn->rem_cfg->name);
272
273 if (vpn->tun_ip)
274 value_node_add(vn_host, "tunnel", vpn->tun_ip);
275
276 if (remote)
277 value_node_add(vn_host, "remote", remote);
278
Max9a852f22019-01-31 15:58:57 +0100279 if (vpn->connected) { /* re-trigger state command */
280 msgb_printf(msg, "state\n");
281 osmo_stream_cli_send(vpn->mgmt, msg);
282 }
283
284 return 0;
285}
286
287/* called once on startup before config file parsing */
288int osysmon_openvpn_init()
289{
290 install_element(CONFIG_NODE, &cfg_openvpn_cmd);
291 install_element(CONFIG_NODE, &cfg_no_openvpn_cmd);
292
293 return 0;
294}
295
296/* called periodically */
297int osysmon_openvpn_poll(struct value_node *parent)
298{
299 int num_vpns = llist_count(&g_oss->openvpn_clients);
300 if (num_vpns) {
301 struct value_node *vn_vpn = value_node_add(parent, "OpenVPN", NULL);
302 struct openvpn_client *vpn;
303 llist_for_each_entry(vpn, &g_oss->openvpn_clients, list)
304 openvpn_client_poll(vpn, vn_vpn);
305 }
306
307 return num_vpns;
308}