]> git.ipfire.org Git - telemetry.git/commitdiff
sources: Add source for hostapd clients
authorMichael Tremer <michael.tremer@ipfire.org>
Mon, 20 Oct 2025 13:26:58 +0000 (13:26 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Mon, 20 Oct 2025 13:26:58 +0000 (13:26 +0000)
Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
Makefile.am
src/daemon/sources.c
src/daemon/sources/hostapd.c [new file with mode: 0644]
src/daemon/sources/hostapd.h [new file with mode: 0644]

index a2d13dac7963f62e332204f49d5a46c853822933..eed0d4576961e8382dc5c64f8b4c9ee02924c462 100644 (file)
@@ -147,6 +147,8 @@ dist_telemetryd_SOURCES = \
        src/daemon/sources/contextswitches.h \
        src/daemon/sources/df.c \
        src/daemon/sources/df.h \
+       src/daemon/sources/hostapd.c \
+       src/daemon/sources/hostapd.h \
        src/daemon/sources/ipfrag4.c \
        src/daemon/sources/ipfrag4.h \
        src/daemon/sources/loadavg.c \
index d237836e924bb5e04b6f870fc7219adb841c182f..2d585aeec0be74231048d3e61450d44d9d70ec5b 100644 (file)
@@ -32,6 +32,7 @@
 #include "sources/conntrack.h"
 #include "sources/contextswitches.h"
 #include "sources/df.h"
+#include "sources/hostapd.h"
 #include "sources/ipfrag4.h"
 #include "sources/loadavg.h"
 #include "sources/memory.h"
@@ -55,6 +56,7 @@ static const td_source_impl* source_impls[] = {
        &conntrack_source,
        &contextswitches_source,
        &df_source,
+       &hostapd_source,
        &ipfrag4_source,
        &loadavg_source,
        &memory_source,
diff --git a/src/daemon/sources/hostapd.c b/src/daemon/sources/hostapd.c
new file mode 100644 (file)
index 0000000..d5ab2e4
--- /dev/null
@@ -0,0 +1,255 @@
+/*#############################################################################
+#                                                                             #
+# 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/>.       #
+#                                                                             #
+#############################################################################*/
+
+#include <errno.h>
+#include <net/ethernet.h>
+#include <netinet/ether.h>
+
+#include "../command.h"
+#include "../ctx.h"
+#include "../file.h"
+#include "../source.h"
+#include "hostapd.h"
+
+typedef struct hostapd_station {
+       td_source* source;
+
+       // MAC Address
+       struct ether_addr address;
+
+       // Packets
+       size_t rx_packets;
+       size_t tx_packets;
+
+       // Bytes
+       size_t rx_bytes;
+       size_t tx_bytes;
+
+       // Signal
+       long signal;
+       long last_ack_signal;
+
+       // RX Rate Info
+       unsigned long rx_rate;
+       unsigned long rx_mcs;
+       unsigned long rx_vhtmcs;
+       unsigned long rx_vhtnss;
+
+       // TX Rate Info
+       unsigned long tx_rate;
+       unsigned long tx_mcs;
+       unsigned long tx_vhtmcs;
+       unsigned long tx_vhtnss;
+
+       // Connected Time
+       unsigned long connected_time;
+
+       // Inactive Time
+       unsigned long inactive_msec;
+
+       // Set if this struct contains some useful data
+       int initialized;
+} hostapd_station;
+
+// Check if a MAC address is all zero
+static int ether_is_zero(const struct ether_addr* address) {
+       for (unsigned int i = 0; i < ETH_ALEN; i++) {
+               if (address->ether_addr_octet[i])
+                       return 0;
+       }
+
+       return 1;
+}
+
+static int hostapd_submit_station(td_ctx* ctx, hostapd_station* station) {
+       char address[ETHER_MAX_LEN];
+       char* p = NULL;
+
+       // Skip if we don't have a MAC address
+       if (ether_is_zero(&station->address))
+               return 0;
+
+       // Format the address
+       p = ether_ntoa_r(&station->address, address);
+       if (!p) {
+               DEBUG(ctx, "Failed to format MAC address: %m\n");
+               return -errno;
+       }
+
+       // Submit the station
+       return td_source_submit(station->source, address,
+               "%lu:%lu:%ld:%ld:%lu:%lu:%lu:%lu:%lu:%lu:%lu:%lu:%lu:%lu:%lu:%lu",
+               station->connected_time, station->inactive_msec,
+               station->signal, station->last_ack_signal,
+               station->rx_packets, station->tx_packets, station->rx_bytes, station->tx_bytes,
+               station->rx_rate * 100, station->rx_mcs, station->rx_vhtmcs, station->rx_vhtnss,
+               station->tx_rate * 100, station->tx_mcs, station->tx_vhtmcs, station->tx_vhtnss);
+}
+
+static int hostapd_parse(td_ctx* ctx, td_file* stdout, unsigned long lineno,
+               char* line, size_t length, void* data) {
+       hostapd_station* station = data;
+       struct ether_addr address = {};
+       struct ether_addr* a = NULL;
+       int r;
+
+       // The format of the output is as follows. Each station's block starts with
+       // the MAC address of the station followed by attributes in key-value format.
+
+       // Try parsing an ethernet address
+       a = ether_aton_r(line, &address);
+       if (a) {
+               // Submit the previous station
+               r = hostapd_submit_station(ctx, station);
+               if (r < 0)
+                       return r;
+
+               // Store the new MAC address
+               station->address = address;
+
+               // Mark as initialized
+               station->initialized = 1;
+
+               // Done processing this line
+               return 0;
+       }
+
+       // Parse all fields
+       td_file_parser parser[] = {
+               PARSE1("rx_packets=%lu", &station->rx_packets),
+               PARSE1("tx_packets=%lu", &station->tx_packets),
+               PARSE1("rx_bytes=%lu", &station->rx_bytes),
+               PARSE1("tx_bytes=%lu", &station->tx_bytes),
+
+               // Signal
+               PARSE1("signal=%ld", &station->signal),
+               PARSE1("last_ack_signal=%lu", &station->last_ack_signal),
+
+               // RX Rate Info
+               PARSE3("rx_rate_info=%lu vhtmcs %lu vhtnss %lu",
+                       &station->rx_rate, &station->rx_vhtmcs, &station->rx_vhtnss),
+               PARSE2("rx_rate_info=%lu mcs %lu",
+                       &station->rx_rate, &station->rx_mcs),
+               PARSE1("rx_rate_info=%lu",
+                       &station->rx_rate),
+
+               // TX Rate Info
+               PARSE3("tx_rate_info=%lu vhtmcs %lu vhtnss %lu",
+                       &station->tx_rate, &station->tx_vhtmcs, &station->tx_vhtnss),
+               PARSE2("tx_rate_info=%lu mcs %lu",
+                       &station->tx_rate, &station->tx_mcs),
+               PARSE1("tx_rate_info=%lu",
+                       &station->tx_rate),
+
+               // Connected Time
+               PARSE1("connected_time=%lu", &station->connected_time),
+
+               // Inactive Time
+               PARSE1("inactive_msec=%lu", &station->inactive_msec),
+
+               { NULL },
+       };
+
+       //  Try parsing the line
+       return td_file_parse_line(stdout, parser, line, length);
+}
+
+static int hostapd_on_success(td_ctx* ctx,
+               int rc, td_file* stdout, void* data) {
+       td_source* source = data;
+       int r;
+
+       // Gather station information
+       hostapd_station station = {
+               .source = source,
+       };
+
+       // Parse the output
+       r = td_file_walk(stdout, hostapd_parse, &station);
+       if (r < 0)
+               return r;
+
+       // Submit the last station
+       return hostapd_submit_station(ctx, &station);
+}
+
+static int hostapd_heartbeat(td_ctx* ctx, td_source* source) {
+       td_command* command = NULL;
+       int r;
+
+       // Run hostapd_cli to fetch station information
+       const char* argv[] = { "hostapd_cli", "all_sta", NULL };
+
+       // Create a new command
+       r = td_source_create_command(source, &command);
+       if (r < 0)
+               goto ERROR;
+
+       // Register the success callback
+       td_command_on_success(command, hostapd_on_success, source);
+
+       // Execute the command
+       r = td_command_execute(command, argv);
+
+ERROR:
+       if (command)
+               td_command_unref(command);
+
+       return r;
+}
+
+const td_source_impl hostapd_source = {
+       .name    = "hostapd",
+
+       // RRD Data Sources
+       .rrd_dss = {
+               { "connected_time",  "COUNTER",  0, -1, },
+               { "inactive_msec",   "GAUGE",    0, -1, },
+
+               // Signal
+               { "signal",          "GAUGE",   -1,  0, },
+               { "last_ack_signal", "GAUGE",   -1,  0, },
+
+               // Packets
+               { "rx_packets",      "DERIVE",   0, -1, },
+               { "tx_packets",      "DERIVE",   0, -1, },
+
+               // Bytes
+               { "rx_bytes",        "DERIVE",   0, -1, },
+               { "tx_bytes",        "DERIVE",   0, -1, },
+
+               // RX Rate Info
+               { "rx_rate",         "GAUGE",    0, -1, },
+               { "rx_mcs",          "GAUGE",    0, -1, },
+               { "rx_vhtmcs",       "GAUGE",    0, -1, },
+               { "rx_vhtnss",       "GAUGE",    0, -1, },
+
+               // TX Rate Info
+               { "tx_rate",         "GAUGE",    0, -1, },
+               { "tx_mcs",          "GAUGE",    0, -1, },
+               { "tx_vhtmcs",       "GAUGE",    0, -1, },
+               { "tx_vhtnss",       "GAUGE",    0, -1, },
+
+               { NULL },
+       },
+
+       // Methods
+       .heartbeat = hostapd_heartbeat,
+};
diff --git a/src/daemon/sources/hostapd.h b/src/daemon/sources/hostapd.h
new file mode 100644 (file)
index 0000000..c476388
--- /dev/null
@@ -0,0 +1,28 @@
+/*#############################################################################
+#                                                                             #
+# 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_HOSTAPD_H
+#define TELEMETRY_SOURCE_HOSTAPD_H
+
+#include "../source.h"
+
+extern const td_source_impl hostapd_source;
+
+#endif /* TELEMETRY_SOURCE_HOSTAPD_H */