From: Marco Chiappero Date: Fri, 25 Sep 2009 07:18:02 +0000 (+0200) Subject: openvpn plugin: Add support for more versions of the “status file” (and more). X-Git-Tag: collectd-4.9.0~37^2~13 X-Git-Url: https://git.verplant.org/?a=commitdiff_plain;h=596abb464fcbf1eced599019cd107d28917bdd3f;p=collectd.git openvpn plugin: Add support for more versions of the “status file” (and more). Hi all, I have some news regarding the Openvpn plugin. I recently upgraded to 4.7.2 and enabled the new (to me) openvpn plugin, but unfortunately it didn't work and I couldn't understand why. So had a look at the code and applied some changes. Here a list of things added: - support for multiple vpn/status files - support for single endpoint status file and for multicontext version 1,3 (currently every status file type is supported) - status file type auto-detection - collection of overhead and compression stats for single mode status files - "NoCompression" option to disable compression statistics collection - new file naming schema correcting the previous wrong one - debug and error output The plugin has already been tested with my own openvpn setup and many different files found on the net, but two major aspects have to be taken into consideration. The first one is the new overhead collection and the change in the compression statistics (now data is stored as they are [1]). However, due to a bug in the previous code, no compression collection was working and so the new one is not going to brake backward compatibility. Unfortunately backward compatibility is broken by the new naming schema, more collectd compliant (and necessary with multiple conf/status files support). So plugin_istance is now the status file name (or the status entry number), while the plugin_type is now the hostname when in multicontext mode or the traffic and overhead when in single. About the plugin_instance value, I started using numbers like the cpu plugin does, but I prefer to use the self explanatory file name, helping in finding the right vpn and avoiding to mess things up when adding a new status file. The downside is that filenames have to be unique (man pages should mention this [2]), therefore I added a check. Nonetheless I think it's very unlikely to happen, it is quite rare to find many vpn on the same system and it's really hard to have the same log filename stored in different dirs rather than the opposite; usually to different tunnels should already correspond different status log name. However, Florian, feel free to choose the other method if you prefer, I can send you a patch to revert to the number based schema. Hope you like the code. Regards, Marco [1] This allows to retrieve multiple informations, not just the pure compression ratio. For example we can now calculate the pure compression ratio, the global compression level, the byte saved (eg. for a overhead vs. saved bytes graph) and so on. [2] Man pages should be updated as well, but I haven't written anything yet. --- diff --git a/src/openvpn.c b/src/openvpn.c index 9f386f0d..bf8511c3 100644 --- a/src/openvpn.c +++ b/src/openvpn.c @@ -2,6 +2,7 @@ * collectd - src/openvpn.c * Copyright (C) 2008 Doug MacEachern * Copyright (C) 2009 Florian octo Forster + * Copyright (C) 2009 Marco Chiappero * * 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 @@ -19,53 +20,71 @@ * Authors: * Doug MacEachern * Florian octo Forster + * Marco Chiappero **/ #include "collectd.h" #include "common.h" #include "plugin.h" -#define DEFAULT_STATUS_FILE "/etc/openvpn/openvpn-status.log" -#define CLIENT_LIST_PREFIX "CLIENT_LIST," +#define V1STRING "Common Name,Real Address,Bytes Received,Bytes Sent,Connected Since\n" +#define V2STRING "HEADER,CLIENT_LIST,Common Name,Real Address,Virtual Address,Bytes Received,Bytes Sent,Connected Since,Connected Since (time_t)\n" +#define V3STRING "HEADER CLIENT_LIST Common Name Real Address Virtual Address Bytes Received Bytes Sent Connected Since Connected Since (time_t)\n" -static char *status_file = NULL; -/* For compression stats we need to convert these counters to a rate. */ -static counter_t pre_compress_old = 0; -static counter_t post_compress_old = 0; -static counter_t pre_decompress_old = 0; -static counter_t post_decompress_old = 0; -static int compression_counter_valid = 0; +struct vpn_status_s +{ + char *file; + enum + { + MULTI1 = 1, /* status-version 1 */ + MULTI2, /* status-version 2 */ + MULTI3, /* status-version 3 */ + SINGLE = 10 /* currently no versions for single mode, maybe in the future */ + } version; + char *name; +}; +typedef struct vpn_status_s vpn_status_t; + +static vpn_status_t **vpn_list = NULL; +static int vpn_num = 0; + +static int store_compression = 1; static const char *config_keys[] = { - "StatusFile" + "StatusFile", + "NoCompression" }; static int config_keys_num = STATIC_ARRAY_SIZE (config_keys); -/* copy-n-pasted from common.c - changed delim to "," */ + +/* Helper function */ +/* copy-n-pasted from common.c - changed delim to "," */ static int openvpn_strsplit (char *string, char **fields, size_t size) { - size_t i; - char *ptr; - char *saveptr; - - i = 0; - ptr = string; - saveptr = NULL; - while ((fields[i] = strtok_r (ptr, ",", &saveptr)) != NULL) - { - ptr = NULL; - i++; - - if (i >= size) - break; - } - - return (i); + size_t i; + char *ptr; + char *saveptr; + + i = 0; + ptr = string; + saveptr = NULL; + while ((fields[i] = strtok_r (ptr, ",", &saveptr)) != NULL) + { + ptr = NULL; + i++; + + if (i >= size) + break; + } + + return (i); } /* int openvpn_strsplit */ -static void openvpn_submit (char *name, counter_t rx, counter_t tx) + +/* dispatches stats about traffic (TCP or UDP) generated by the tunnel per single endpoint */ +static void iostats_submit (char *name, char *type, counter_t rx, counter_t tx) { value_t values[2]; value_list_t vl = VALUE_LIST_INIT; @@ -73,163 +92,474 @@ static void openvpn_submit (char *name, counter_t rx, counter_t tx) values[0].counter = rx; values[1].counter = tx; + /* NOTE: using plugin_instance to identify each vpn config (and + * status) file; using type_instance to identify the endpoint + * host when in multimode, traffic or overhead when in single. + */ + vl.values = values; vl.values_len = STATIC_ARRAY_SIZE (values); sstrncpy (vl.host, hostname_g, sizeof (vl.host)); sstrncpy (vl.plugin, "openvpn", sizeof (vl.plugin)); sstrncpy (vl.plugin_instance, name, sizeof (vl.plugin_instance)); - sstrncpy (vl.type, "if_octets", sizeof (vl.type)); + sstrncpy (vl.type, "io_octets", sizeof (vl.type)); + sstrncpy (vl.type_instance, type, sizeof (vl.type_instance)); plugin_dispatch_values (&vl); -} /* void openvpn_submit */ +} /* void traffic_submit */ -static void compression_submit (char *type_instance, gauge_t ratio) +/* dispatches stats about data compression shown when in single mode */ +static void compression_submit (char *name, char *type, counter_t uncompressed, counter_t compressed) { - value_t values[1]; + value_t values[2]; value_list_t vl = VALUE_LIST_INIT; - values[0].gauge = ratio; + values[0].counter = uncompressed; + values[1].counter = compressed; vl.values = values; vl.values_len = STATIC_ARRAY_SIZE (values); sstrncpy (vl.host, hostname_g, sizeof (vl.host)); sstrncpy (vl.plugin, "openvpn", sizeof (vl.plugin)); - sstrncpy (vl.type, "compression_ratio", sizeof (vl.type)); - sstrncpy (vl.type_instance, type_instance, sizeof (vl.type)); + sstrncpy (vl.plugin_instance, name, sizeof (vl.plugin_instance)); + sstrncpy (vl.type, "compression", sizeof (vl.type)); + sstrncpy (vl.type_instance, type, sizeof (vl.type_instance)); plugin_dispatch_values (&vl); } /* void compression_submit */ -static int openvpn_read (void) +static int single_read (char *name, FILE *fh) { - FILE *fh; char buffer[1024]; - char *fields[10]; + char *fields[4]; const int max_fields = STATIC_ARRAY_SIZE (fields); - int fields_num; + int fields_num, read = 0; + + counter_t link_rx, link_tx; + counter_t tun_rx, tun_tx; + counter_t pre_compress, post_compress; + counter_t pre_decompress, post_decompress; + counter_t overhead_rx, overhead_tx; + + link_rx = 0; + link_tx = 0; + tun_rx = 0; + tun_tx = 0; + pre_compress = 0; + post_compress = 0; + pre_decompress = 0; + post_decompress = 0; + overhead_rx = 0; + overhead_tx = 0; - counter_t pre_compress_new = 0; - counter_t post_compress_new = 0; - counter_t pre_decompress_new = 0; - counter_t post_decompress_new = 0; - /* Clear the least significant four bits, just to make sure all four - * counters above are considered to be invalid. */ - compression_counter_valid &= ~0x0f; - - fh = fopen ((status_file != NULL) - ? status_file - : DEFAULT_STATUS_FILE, "r"); - if (fh == NULL) - return (-1); - - /* status file is generated by openvpn/multi.c:multi_print_status() - * this plugin requires server.conf: "status-version 2" - * http://svn.openvpn.net/projects/openvpn/trunk/openvpn/multi.c - */ while (fgets (buffer, sizeof (buffer), fh) != NULL) { fields_num = openvpn_strsplit (buffer, fields, max_fields); - /* Expect at least ``key,value''. */ - if (fields_num < 2) + /* status file is generated by openvpn/sig.c:print_status() + * http://svn.openvpn.net/projects/openvpn/trunk/openvpn/sig.c + * + * The line we're expecting has 2 fields. We ignore all lines + * with more or less fields. + */ + if (fields_num != 2) + { continue; + } + else + { + if (strcmp (fields[0], "TUN/TAP read bytes") == 0) + { + /* read from the system and sent over the tunnel */ + tun_tx = atoll (fields[1]); + } + else if (strcmp (fields[0], "TUN/TAP write bytes") == 0) + { + /* read from the tunnel and written in the system */ + tun_rx = atoll (fields[1]); + } + else if (strcmp (fields[0], "TCP/UDP read bytes") == 0) + { + link_rx = atoll (fields[1]); + } + else if (strcmp (fields[0], "TCP/UDP write bytes") == 0) + { + link_tx = atoll (fields[1]); + } + else if (strcmp (fields[0], "pre-compress bytes") == 0) + { + pre_compress = atoll (fields[1]); + } + else if (strcmp (fields[0], "post-compress bytes") == 0) + { + post_compress = atoll (fields[1]); + } + else if (strcmp (fields[0], "pre-decompress bytes") == 0) + { + pre_decompress = atoll (fields[1]); + } + else if (strcmp (fields[0], "post-decompress bytes") == 0) + { + post_decompress = atoll (fields[1]); + } + } + } + + iostats_submit (name, "traffic", link_rx, link_tx); + + /* we need to force this order to avoid negative values with these unsigned */ + overhead_rx = (((link_rx - pre_decompress) + post_decompress) - tun_rx); + overhead_tx = (((link_tx - post_compress) + pre_compress) - tun_tx); + + iostats_submit (name, "overhead", overhead_rx, overhead_tx); + + if (store_compression) + { + compression_submit (name, "data_in", post_decompress, pre_decompress); + compression_submit (name, "data_out", pre_compress, post_compress); + } + + read = 1; + + return (read); +} /* int single_read */ - if (strcmp (fields[0], "CLIENT_LIST") == 0) +/* for reading status version 1 */ +static int multi1_read (char *name, FILE *fh) +{ + char buffer[1024]; + char *fields[10]; + const int max_fields = STATIC_ARRAY_SIZE (fields); + int fields_num, read = 0, skip = 1; + + /* read the file until the "ROUTING TABLE" line is found (no more info after) */ + for ( ; strcmp (buffer, "ROUTING TABLE\n"); fgets (buffer, sizeof (buffer), fh)) + { + if (skip) /* skip the first lines until the client list section is found */ { - char *name; - counter_t rx; - counter_t tx; + /* we can't start reading data until this string is found */ + if (strcmp (buffer, V1STRING) == 0) + skip = 0; - /* The line we're expecting has 8 fields. We ignore all lines - * with more or less fields. */ - if (fields_num != 8) - continue; + continue; + } + else + { + fields_num = openvpn_strsplit (buffer, fields, max_fields); - name = fields[1]; /* "Common Name" */ - rx = atoll (fields[4]); /* "Bytes Received */ - tx = atoll (fields[5]); /* "Bytes Sent" */ - openvpn_submit (name, rx, tx); + iostats_submit (name, /* vpn instance */ + fields[0], /* "Common Name" */ + atoll (fields[2]), /* "Bytes Received" */ + atoll (fields[3])); /* "Bytes Sent" */ + read = 1; } - else if (strcmp (fields[0], "pre-compress") == 0) + } + + return (read); +} /* int multi1_read */ + +/* for reading status version 2 */ +static int multi2_read (char *name, FILE *fh) +{ + char buffer[1024]; + char *fields[10]; + const int max_fields = STATIC_ARRAY_SIZE (fields); + int fields_num, read = 0; + + while (fgets (buffer, sizeof (buffer), fh) != NULL) + { + fields_num = openvpn_strsplit (buffer, fields, max_fields); + + /* status file is generated by openvpn/multi.c:multi_print_status() + * http://svn.openvpn.net/projects/openvpn/trunk/openvpn/multi.c + * + * The line we're expecting has 8 fields. We ignore all lines + * with more or less fields. + */ + if (fields_num != 8) { - pre_compress_new = atoll (fields[1]); - compression_counter_valid |= 0x01; + continue; } - else if (strcmp (fields[0], "post-compress") == 0) + else { - post_compress_new = atoll (fields[1]); - compression_counter_valid |= 0x02; + if (strcmp (fields[0], "CLIENT_LIST") == 0) + { + iostats_submit (name, /* vpn instance */ + fields[1], /* "Common Name" */ + atoll (fields[4]), /* "Bytes Received" */ + atoll (fields[5])); /* "Bytes Sent" */ + read = 1; + } } - else if (strcmp (fields[0], "pre-decompress") == 0) + } + + return (read); +} /* int multi2_read */ + +/* for reading status version 3 */ +static int multi3_read (char *name, FILE *fh) +{ + char buffer[1024]; + char *fields[15]; + const int max_fields = STATIC_ARRAY_SIZE (fields); + int fields_num, read = 0; + + while (fgets (buffer, sizeof (buffer), fh) != NULL) + { + fields_num = strsplit (buffer, fields, max_fields); + + /* status file is generated by openvpn/multi.c:multi_print_status() + * http://svn.openvpn.net/projects/openvpn/trunk/openvpn/multi.c + * + * The line we're expecting has 12 fields. We ignore all lines + * with more or less fields. + */ + if (fields_num != 12) { - pre_decompress_new = atoll (fields[1]); - compression_counter_valid |= 0x04; + continue; } - else if (strcmp (fields[0], "post-decompress") == 0) + else { - post_decompress_new = atoll (fields[1]); - compression_counter_valid |= 0x08; + if (strcmp (fields[0], "CLIENT_LIST") == 0) + { + iostats_submit (name, /* vpn instance */ + fields[1], /* "Common Name" */ + atoll (fields[4]), /* "Bytes Received" */ + atoll (fields[5])); /* "Bytes Sent" */ + read = 1; + } } } - fclose (fh); - /* Check that all four counters are valid, {pre,post}_*_{old,new}. */ - if ((compression_counter_valid & 0x33) == 0x33) + return (read); +} /* int multi3_read */ + +/* read callback */ +static int openvpn_read (void) +{ + FILE *fh; + int i, read; + + read = 0; + + /* call the right read function for every status entry in the list */ + for (i = 0; i < vpn_num; i++) { - counter_t pre_diff; - counter_t post_diff; + fh = fopen (vpn_list[i]->file, "r"); + if (fh == NULL) + { + char errbuf[1024]; + WARNING ("openvpn plugin: fopen(%s) failed: %s", vpn_list[i]->file, + sstrerror (errno, errbuf, sizeof (errbuf))); - pre_diff = counter_diff (pre_compress_old, pre_compress_new); - post_diff = counter_diff (post_compress_old, post_compress_new); + continue; + } - /* If we compress, we're sending. */ - compression_submit ("tx", - ((gauge_t) post_diff) / ((gauge_t) pre_diff)); + switch (vpn_list[i]->version) + { + case SINGLE: + read = single_read(vpn_list[i]->name, fh); + break; + + case MULTI1: + read = multi1_read(vpn_list[i]->name, fh); + break; + + case MULTI2: + read = multi2_read(vpn_list[i]->name, fh); + break; + + case MULTI3: + read = multi3_read(vpn_list[i]->name, fh); + break; + } + + fclose (fh); } - /* Now check the other found counters. */ - if ((compression_counter_valid & 0xcc) == 0xcc) - { - counter_t pre_diff; - counter_t post_diff; + return (read ? 0 : -1); +} /* int openvpn_read */ - pre_diff = counter_diff (pre_decompress_old, pre_decompress_new); - post_diff = counter_diff (post_decompress_old, post_decompress_new); +static int version_detect (FILE *fh) +{ + char buffer[1024]; + int version = 0; - /* If we decompress, we're receiving. */ - compression_submit ("rx", - ((gauge_t) pre_diff) / ((gauge_t) post_diff)); + /* we look at the first line searching for SINGLE mode configuration */ + if ((fscanf (fh, "%*s %s", buffer) == 1) && (strcmp (buffer, "STATISTICS") == 0)) + { + DEBUG ("openvpn plugin: found status file version SINGLE"); + version = SINGLE; } + else /* else multimode */ + { + /* now search for the specific multimode data format */ + while ((fgets (buffer, sizeof (buffer), fh)) != NULL) + { - /* Now copy all the new counters to the old counters and move the flags - * up. */ - pre_compress_old = pre_compress_new; - post_compress_old = post_compress_new; - pre_decompress_old = pre_decompress_new; - post_decompress_old = post_decompress_new; - compression_counter_valid = (compression_counter_valid & 0x0f) << 4; + /* searching for multi version 1 */ + if (strcmp (buffer, V1STRING) == 0) + { + DEBUG ("openvpn plugin: found status file version MULTI1"); + version = MULTI1; + break; + } + /* searching for multi version 2 */ + else if (strcmp (buffer, V2STRING) == 0) + { + DEBUG ("openvpn plugin: found status file version MULTI2"); + version = MULTI2; + break; + } + /* searching for multi version 3 */ + else if (strcmp (buffer, V3STRING) == 0) + { + DEBUG ("openvpn plugin: found status file version MULTI3"); + version = MULTI3; + break; + } + } + } - return (0); -} /* int openvpn_read */ + if (version == 0) + { + DEBUG ("openvpn plugin: unknown file format, please report this as bug"); + } + + return version; +} /* int version_detect */ static int openvpn_config (const char *key, const char *value) { if (strcasecmp ("StatusFile", key) == 0) { - sfree (status_file); + FILE *fh; + char *status_file, *status_name, *filename; + int status_version, i; + vpn_status_t *temp; + + /* check whether the status file provided is readable */ + fh = fopen (value, "r"); + if (fh == NULL) + { + char errbuf[1024]; + WARNING ("openvpn plugin: unable to read \"%s\": %s", + value, sstrerror (errno, errbuf, sizeof (errbuf))); + return (1); + } + + /* once open try to detect the status file format */ + status_version = version_detect (fh); + + fclose (fh); + + if (status_version == 0) + { + WARNING ("openvpn plugin: unable to detect status version, \ + discarding status file \"%s\".", value); + return (1); + } + status_file = sstrdup (value); + if (status_file == NULL) + { + char errbuf[1024]; + WARNING ("openvpn plugin: sstrdup failed: %s", + sstrerror (errno, errbuf, sizeof (errbuf))); + return (1); + } + + /* it determines the file name as string starting at location filename + 1 */ + filename = strrchr (status_file, (int) '/'); + if (filename == NULL) + { + /* status_file is already the file name only */ + status_name = status_file; + } + else + { + /* doesn't waist memory, uses status_file starting at filename + 1 */ + status_name = filename + 1; + } + + /* if not empty, it scans the list looking for a clone */ + if (vpn_num) + { + for (i = 0; i < vpn_num; i++) + { + if (strcasecmp (vpn_list[i]->name, status_name) == 0) + { + WARNING ("status filename \"%s\" already used, \ + please choose a different one.", status_name); + return (1); + } + } + } + + /* create a new vpn element since file and version are ok */ + temp = (vpn_status_t *) malloc (sizeof (vpn_status_t)); + temp->file = status_file; + temp->version = status_version; + temp->name = status_name; + + vpn_list = (vpn_status_t **) realloc (vpn_list, (vpn_num + 1) * sizeof (vpn_status_t *)); + if (vpn_list == NULL) + { + char errbuf[1024]; + ERROR ("openvpn plugin: malloc failed: %s", + sstrerror (errno, errbuf, sizeof (errbuf))); + return (1); + } + + vpn_list[vpn_num] = temp; + vpn_num++; + + DEBUG ("openvpn plugin: status file \"%s\" added", temp->file); + + } + else if (strcasecmp ("NoCompression", key) == 0) + { + if ((strcasecmp ("True", value) == 0) + || (strcasecmp ("Yes", value) == 0) + || (strcasecmp ("On", value) == 0)) + { + store_compression = 0; + DEBUG ("openvpn plugin: no 'compression statistcs' collected"); + } + else + { + store_compression = 1; + } } else { return (-1); } - return (0); + + return (0); } /* int openvpn_config */ +/* shutdown callback */ +static int openvpn_shutdown (void) +{ + int i; + + for (i = 0; i < vpn_num; i++) + { + sfree (vpn_list[i]->file); + sfree (vpn_list[i]); + } + + sfree (vpn_list); + + return (0); +} /* int openvpn_shutdown */ + void module_register (void) { plugin_register_config ("openvpn", openvpn_config, config_keys, config_keys_num); plugin_register_read ("openvpn", openvpn_read); + plugin_register_shutdown ("openvpn", openvpn_shutdown); } /* void module_register */ diff --git a/src/types.db b/src/types.db index c8c6b98a..60e550f6 100644 --- a/src/types.db +++ b/src/types.db @@ -16,6 +16,7 @@ cache_ratio value:GAUGE:0:100 cache_result value:COUNTER:0:4294967295 cache_size value:GAUGE:0:4294967295 charge value:GAUGE:0:U +compression uncompressed:COUNTER:0:U, compressed:COUNTER:0:U compression_ratio value:GAUGE:0:2 connections value:COUNTER:0:U conntrack entropy:GAUGE:0:4294967295