From: Michael Tremer Date: Mon, 20 Oct 2025 13:26:58 +0000 (+0000) Subject: sources: Add source for hostapd clients X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b79c796ad2d9304d2ef392ee110fa0b5e4d1a34e;p=telemetry.git sources: Add source for hostapd clients Signed-off-by: Michael Tremer --- diff --git a/Makefile.am b/Makefile.am index a2d13da..eed0d45 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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 \ diff --git a/src/daemon/sources.c b/src/daemon/sources.c index d237836..2d585ae 100644 --- a/src/daemon/sources.c +++ b/src/daemon/sources.c @@ -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 index 0000000..d5ab2e4 --- /dev/null +++ b/src/daemon/sources/hostapd.c @@ -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 . # +# # +#############################################################################*/ + +#include +#include +#include + +#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 index 0000000..c476388 --- /dev/null +++ b/src/daemon/sources/hostapd.h @@ -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 . # +# # +#############################################################################*/ + +#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 */