From: Michael Tremer Date: Wed, 3 Dec 2025 15:43:22 +0000 (+0000) Subject: sources: Add a source to collect OpenVPN client traffic X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c7ae84dc88dda20e4f8e814e2a11522f4679b013;p=telemetry.git sources: Add a source to collect OpenVPN client traffic Signed-off-by: Michael Tremer --- diff --git a/Makefile.am b/Makefile.am index 6171b79..9fd9120 100644 --- a/Makefile.am +++ b/Makefile.am @@ -196,6 +196,8 @@ dist_telemetryd_SOURCES = \ src/daemon/sources/memory.h \ src/daemon/sources/nftables.c \ src/daemon/sources/nftables.h \ + src/daemon/sources/openvpn.c \ + src/daemon/sources/openvpn.h \ src/daemon/sources/pressure-cpu.c \ src/daemon/sources/pressure-cpu.h \ src/daemon/sources/pressure-io.c \ diff --git a/configure.ac b/configure.ac index 73a0640..32b41c4 100644 --- a/configure.ac +++ b/configure.ac @@ -222,6 +222,7 @@ AC_SOURCE([legacy-gateway-latency4], [$have_libnl3 $have_libnl3_route]) AC_SOURCE([loadavg]) AC_SOURCE([memory]) AC_SOURCE([nftables], [$have_libmnl $have_libnftnl]) +AC_SOURCE([openvpn]) AC_SOURCE([pressure-cpu]) AC_SOURCE([pressure-io]) AC_SOURCE([pressure-memory]) diff --git a/src/daemon/sources.c b/src/daemon/sources.c index a9ce8b6..81d93d0 100644 --- a/src/daemon/sources.c +++ b/src/daemon/sources.c @@ -42,6 +42,7 @@ #include "sources/loadavg.h" #include "sources/memory.h" #include "sources/nftables.h" +#include "sources/openvpn.h" #include "sources/pressure-cpu.h" #include "sources/pressure-io.h" #include "sources/pressure-memory.h" @@ -113,6 +114,10 @@ static const td_source_impl* source_impls[] = { &nftables_source, #endif /* BUILD_SOURCE_NFTABLES */ +#ifdef BUILD_SOURCE_OPENVPN + &openvpn_source, +#endif /* BUILD_SOURCE_OPENVPN */ + #ifdef BUILD_SOURCE_PRESSURE_CPU &pressure_cpu_source, #endif /* BUILD_SOURCE_PRESSURE_CPU */ diff --git a/src/daemon/sources/openvpn.c b/src/daemon/sources/openvpn.c new file mode 100644 index 0000000..90040b8 --- /dev/null +++ b/src/daemon/sources/openvpn.c @@ -0,0 +1,225 @@ +/*############################################################################# +# # +# telemetryd - The IPFire Telemetry Collection Service # +# Copyright (C) 2025 IPFire Development Team # +# # +# 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 3 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, see . # +# # +#############################################################################*/ + +#ifdef BUILD_SOURCE_OPENVPN + +#include "../ctx.h" +#include "../source.h" +#include "../string.h" +#include "openvpn.h" + +#define OPENVPN_LOG_PATH "/var/run/openvpn-rw.log" + +typedef struct openvpn_fields { + td_source* source; + unsigned int common_name; + unsigned int rx_bytes; + unsigned int tx_bytes; +} openvpn_fields; + +typedef struct openvpn_client { + char* common_name; + size_t rx_bytes; + size_t tx_bytes; +} openvpn_client; + +static void openvpn_escape_name(char* name) { + for (char* p = name; *p; p++) { + switch (*p) { + case ' ': + *p = '-'; + break; + + default: + break; + } + } +} + +/* + This function splits the line by tabs and will call the callback function for each token. +*/ +static int split_line(td_ctx* ctx, char* line, openvpn_fields* fields, + int (*callback)(td_ctx* ctx, openvpn_fields* fields, unsigned int idx, + char* token, void* data), void* data) { + char* p = line; + char* d = NULL; + int r; + + unsigned int idx = 0; + + while (p && *p) { + // Find the next delimiter + d = strchr(p, '\t'); + + // Handle the last token + if (!d) + return callback(ctx, fields, idx++, p, data); + + // Terminate the token + *d = '\0'; + + // Call the callback + r = callback(ctx, fields, idx++, p, data); + if (r < 0) + return r; + + // Advance p + p = d + 1; + } + + return 0; +} + +static int openvpn_parse_header(td_ctx* ctx, openvpn_fields* fields, + unsigned int idx, char* token, void* data) { + switch (idx) { + // The first field must be "HEADER" + case 0: + if (!td_string_equals(token, "HEADER")) + return 0; + break; + + // The second field must be "CLIENT_LIST" + case 1: + if (!td_string_equals(token, "CLIENT_LIST")) + return 0; + break; + + // Store the indexes of all required fields + default: + if (td_string_equals(token, "Common Name")) + fields->common_name = idx - 1; + + else if (td_string_equals(token, "Bytes Received")) + fields->rx_bytes = idx - 1; + + else if (td_string_equals(token, "Bytes Sent")) + fields->tx_bytes = idx - 1; + + break; + } + + return 0; +} + +static int openvpn_parse_client_list(td_ctx* ctx, openvpn_fields* fields, + unsigned int idx, char* token, void* data) { + openvpn_client* client = data; + char* e = NULL; + + // All fields must be found + if (!fields->common_name || !fields->rx_bytes || !fields->tx_bytes) + return -ENOTSUP; + + // Store the common name + if (fields->common_name == idx) + client->common_name = token; + + // Store RX bytes + else if (fields->rx_bytes == idx) + client->rx_bytes = strtoul(token, &e, 10); + + // Store TX bytes + else if (fields->tx_bytes == idx) + client->tx_bytes = strtoul(token, &e, 10); + + // If e is set, we could not parse the entire token + if (e && *e) { + ERROR(ctx, "Failed to parse token: %s\n", token); + return -EINVAL; + } + + return 0; +} + +static int openvpn_parse(td_ctx* ctx, td_file* file, unsigned long lineno, + char* line, size_t length, void* data) { + openvpn_fields* fields = data; + openvpn_client client = {}; + int r; + + // Parse any headers + if (td_string_startswith(line, "HEADER")) + return split_line(ctx, line, fields, openvpn_parse_header, NULL); + + // Parse the client list + else if (td_string_startswith(line, "CLIENT_LIST")) { + r = split_line(ctx, line, fields, openvpn_parse_client_list, &client); + if (r < 0) + return r; + + // Don't submit anything if we could not parse the line + if (!client.common_name) + return 0; + + // Escape the name + openvpn_escape_name(client.common_name); + + // Submit the client + return td_source_submit_values(fields->source, + client.common_name, VALUES( + VALUE_UINT64("rx_bytes", &client.rx_bytes), + VALUE_UINT64("tx_bytes", &client.tx_bytes) + ) + ); + } + + return 0; +} + +static int openvpn_heartbeat(td_ctx* ctx, td_source* source) { + openvpn_fields fields = { + .source = source, + }; + td_file* file = NULL; + int r; + + // Open the OpenVPN log file + r = td_file_open_path(&file, ctx, OPENVPN_LOG_PATH); + if (r < 0) + goto ERROR; + + // Walk through the entire file + r = td_file_walk(file, openvpn_parse, &fields); + +ERROR: + if (file) + td_file_unref(file); + + return r; +} + +const td_source_impl openvpn_source = { + .name = "openvpn", + + // RRD Data Sources + .rrd_dss = { + // some + { "rx_bytes", "DERIVE", 0, -1, }, + { "tx_bytes", "DERIVE", 0, -1, }, + { NULL }, + }, + + // Methods + .heartbeat = openvpn_heartbeat, +}; + +#endif /* BUILD_SOURCE_OPENVPN */ diff --git a/src/daemon/sources/openvpn.h b/src/daemon/sources/openvpn.h new file mode 100644 index 0000000..4d89299 --- /dev/null +++ b/src/daemon/sources/openvpn.h @@ -0,0 +1,30 @@ +/*############################################################################# +# # +# telemetryd - The IPFire Telemetry Collection Service # +# Copyright (C) 2025 IPFire Development Team # +# # +# 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 3 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, see . # +# # +#############################################################################*/ + +#ifndef TELEMETRY_SOURCE_OPENVPN_H +#define TELEMETRY_SOURCE_OPENVPN_H +#ifdef BUILD_SOURCE_OPENVPN + +#include "../source.h" + +extern const td_source_impl openvpn_source; + +#endif /* BUILD_SOURCE_OPENVPN */ +#endif /* TELEMETRY_SOURCE_OPENVPN_H */