]> git.ipfire.org Git - telemetry.git/commitdiff
sources: Add a source to collect OpenVPN client traffic
authorMichael Tremer <michael.tremer@ipfire.org>
Wed, 3 Dec 2025 15:43:22 +0000 (15:43 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Wed, 3 Dec 2025 15:43:22 +0000 (15:43 +0000)
Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
Makefile.am
configure.ac
src/daemon/sources.c
src/daemon/sources/openvpn.c [new file with mode: 0644]
src/daemon/sources/openvpn.h [new file with mode: 0644]

index 6171b7934786ab5d491a2f5e3b2200e102671ceb..9fd912046df0f33102bcaa5a08d8f73b3cae0454 100644 (file)
@@ -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 \
index 73a06404dd76528b0d66ef911bc3cc78f8acc501..32b41c41956d06388693152382dc0a23f3793478 100644 (file)
@@ -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])
index a9ce8b6f52e679ba8c30a798b569f831b6859f6d..81d93d05ec34d01bd6cfb286fb1c2c26517a33ae 100644 (file)
@@ -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 (file)
index 0000000..90040b8
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.       #
+#                                                                             #
+#############################################################################*/
+
+#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 (file)
index 0000000..4d89299
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.       #
+#                                                                             #
+#############################################################################*/
+
+#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 */