From 15240e0819685c30a4955ae161374b5a3fc9d313 Mon Sep 17 00:00:00 2001 From: Michael Tremer Date: Fri, 14 Apr 2023 17:43:12 +0000 Subject: [PATCH] networkd: Collect stats regulary and emit them on dbus This is useful for us monitoring interface throughput (e.g. in collecty). Signed-off-by: Michael Tremer --- Makefile.am | 2 + src/networkd/daemon.c | 44 ++++++++ src/networkd/daemon.h | 6 + src/networkd/link.c | 81 ++++++++++++++ src/networkd/link.h | 6 + src/networkd/port.c | 31 +++++- src/networkd/port.h | 5 + src/networkd/stats-collector.c | 197 +++++++++++++++++++++++++++++++++ src/networkd/stats-collector.h | 37 +++++++ src/networkd/zone.c | 30 +++++ src/networkd/zone.h | 6 + 11 files changed, 444 insertions(+), 1 deletion(-) create mode 100644 src/networkd/stats-collector.c create mode 100644 src/networkd/stats-collector.h diff --git a/Makefile.am b/Makefile.am index 80a72365..22d90d48 100644 --- a/Makefile.am +++ b/Makefile.am @@ -332,6 +332,8 @@ dist_networkd_SOURCES = \ src/networkd/port.h \ src/networkd/port-bus.c \ src/networkd/port-bus.h \ + src/networkd/stats-collector.c \ + src/networkd/stats-collector.h \ src/networkd/string.h \ src/networkd/util.c \ src/networkd/util.h \ diff --git a/src/networkd/daemon.c b/src/networkd/daemon.c index c8e65f41..749a70b8 100644 --- a/src/networkd/daemon.c +++ b/src/networkd/daemon.c @@ -35,6 +35,7 @@ #include "links.h" #include "logging.h" #include "ports.h" +#include "stats-collector.h" #include "zone.h" #include "zones.h" @@ -67,6 +68,8 @@ struct nw_daemon { // Ports nw_ports* ports; + // Stats Collector + sd_event_source* stats_collector_event; }; static int __nw_daemon_terminate(sd_event_source* source, const struct signalfd_siginfo* si, @@ -322,6 +325,33 @@ static int nw_daemon_reconfigure(nw_daemon* daemon) { return 0; } +static int nw_daemon_starts_stats_collector(nw_daemon* daemon) { + sd_event_source* s = NULL; + int r; + + // Register the stats collector main function + r = sd_event_add_time_relative(daemon->loop, &s, CLOCK_MONOTONIC, 0, 0, + nw_stats_collector, daemon); + if (r < 0) { + ERROR("Could not start the stats collector: %m\n"); + goto ERROR; + } + + // Keep calling the stats collector for forever + r = sd_event_source_set_enabled(s, SD_EVENT_ON); + if (r < 0) + goto ERROR; + + // Keep a reference to the event source + daemon->stats_collector_event = sd_event_source_ref(s); + +ERROR: + if (s) + sd_event_source_unref(s); + + return r; +} + static int nw_daemon_setup(nw_daemon* daemon) { int r; @@ -360,6 +390,11 @@ static int nw_daemon_setup(nw_daemon* daemon) { if (r) return r; + // Start the stats collector + r = nw_daemon_starts_stats_collector(daemon); + if (r) + return r; + return 0; } @@ -404,6 +439,8 @@ static void nw_daemon_free(nw_daemon* daemon) { // Cleanup common objects nw_daemon_cleanup(daemon); + if (daemon->stats_collector_event) + sd_event_source_unref(daemon->stats_collector_event); if (daemon->bus) sd_bus_unref(daemon->bus); if (daemon->loop) @@ -498,6 +535,13 @@ int nw_daemon_save(nw_daemon* daemon) { return 0; } +/* + Bus +*/ +sd_bus* nw_daemon_get_bus(nw_daemon* daemon) { + return daemon->bus; +} + /* Netlink */ diff --git a/src/networkd/daemon.h b/src/networkd/daemon.h index a14d33b2..74e19e69 100644 --- a/src/networkd/daemon.h +++ b/src/networkd/daemon.h @@ -21,6 +21,7 @@ #ifndef NETWORKD_DAEMON_H #define NETWORKD_DAEMON_H +#include #include typedef struct nw_daemon nw_daemon; @@ -43,6 +44,11 @@ int nw_daemon_reload(nw_daemon* daemon); int nw_daemon_save(nw_daemon* daemon); +/* + Bus +*/ +sd_bus* nw_daemon_get_bus(nw_daemon* daemon); + /* Netlink */ diff --git a/src/networkd/link.c b/src/networkd/link.c index 09b9a622..1edf20d7 100644 --- a/src/networkd/link.c +++ b/src/networkd/link.c @@ -19,6 +19,7 @@ #############################################################################*/ #include +#include #include #include #include @@ -46,6 +47,9 @@ struct nw_link { NW_LINK_DESTROYED, } state; + // Stats + struct rtnl_link_stats64 stats64; + // MTU uint32_t mtu; uint32_t min_mtu; @@ -121,6 +125,83 @@ const char* nw_link_ifname(nw_link* link) { return link->ifname; } +// Stats + +const struct rtnl_link_stats64* nw_link_get_stats64(nw_link* link) { + return &link->stats64; +} + +static int nw_link_call_getlink(nw_link* link, + int (*callback)(sd_netlink* rtnl, sd_netlink_message* m, void* data)) { + sd_netlink_message* m = NULL; + int r; + + sd_netlink* rtnl = nw_daemon_get_rtnl(link->daemon); + if (!rtnl) + return 1; + + // Create a new message + r = sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, link->ifindex); + if (r < 0) { + ERROR("Could not allocate RTM_GETLINK message: %m\n"); + goto ERROR; + } + + // Send the message + r = sd_netlink_call_async(rtnl, NULL, m, callback, + __nw_link_unref, nw_link_ref(link), -1, NULL); + if (r < 0) { + ERROR("Could not send rtnetlink message: %m\n"); + goto ERROR; + } + +ERROR: + if (m) + sd_netlink_message_unref(m); + + return r; +} + +static int __nw_link_update_stats(sd_netlink* rtnl, sd_netlink_message* m, void* data) { + nw_link* link = (nw_link*)data; + int r; + + // Fetch the stats + r = sd_netlink_message_read(m, IFLA_STATS64, sizeof(link->stats64), &link->stats64); + if (r < 0) + return r; + + DEBUG("Link %d: Stats updated\n", link->ifindex); + + // Log stats + DEBUG(" Packets : RX: %12llu, TX: %12llu\n", + link->stats64.rx_packets, link->stats64.tx_packets); + DEBUG(" Bytes : RX: %12llu, TX: %12llu\n", + link->stats64.rx_bytes, link->stats64.tx_bytes); + DEBUG(" Errors : RX: %12llu, TX: %12llu\n", + link->stats64.rx_errors, link->stats64.tx_errors); + DEBUG(" Dropped : RX: %12llu, TX: %12llu\n", + link->stats64.rx_dropped, link->stats64.rx_dropped); + DEBUG(" Multicast : %llu\n", link->stats64.multicast); + DEBUG(" Collisions : %llu\n", link->stats64.collisions); + + // Notify ports that stats have been updated + r = nw_daemon_ports_walk(link->daemon, __nw_port_update_stats, link); + if (r) + return r; + + // Notify zones that stats have been updated + r = nw_daemon_zones_walk(link->daemon, __nw_zone_update_stats, link); + if (r) + return r; + + return 0; +} + +int nw_link_update_stats(nw_link* link) { + return nw_link_call_getlink(link, __nw_link_update_stats); +} + // Carrier int nw_link_has_carrier(nw_link* link) { diff --git a/src/networkd/link.h b/src/networkd/link.h index 58a825a1..2bab47c5 100644 --- a/src/networkd/link.h +++ b/src/networkd/link.h @@ -21,6 +21,8 @@ #ifndef NETWORKD_LINK_H #define NETWORKD_LINK_H +#include + typedef struct nw_link nw_link; #include "daemon.h" @@ -33,6 +35,10 @@ nw_link* nw_link_unref(nw_link* link); int nw_link_ifindex(nw_link* link); const char* nw_link_ifname(nw_link* link); +// Stats +const struct rtnl_link_stats64* nw_link_get_stats64(nw_link* link); +int nw_link_update_stats(nw_link* link); + int nw_link_has_carrier(nw_link* link); int nw_link_process(sd_netlink* rtnl, sd_netlink_message* message, void* data); diff --git a/src/networkd/port.c b/src/networkd/port.c index 5694024d..7638c7b7 100644 --- a/src/networkd/port.c +++ b/src/networkd/port.c @@ -29,8 +29,9 @@ #include "config.h" #include "link.h" #include "logging.h" -#include "string.h" #include "port.h" +#include "stats-collector.h" +#include "string.h" struct nw_port { nw_daemon* daemon; @@ -357,3 +358,31 @@ int nw_port_has_carrier(nw_port* port) { return nw_link_has_carrier(port->link); } + +/* + Stats +*/ + +const struct rtnl_link_stats64* nw_port_get_stats64(nw_port* port) { + if (!port->link) + return NULL; + + return nw_link_get_stats64(port->link); +} + +int __nw_port_update_stats(nw_daemon* daemon, nw_port* port, void* data) { + nw_link* link = (nw_link*)data; + + // Emit stats if link matches + if (port->link == link) + return nw_stats_collector_emit_port_stats(daemon, port); + + return 0; +} + +int nw_port_update_stats(nw_port* port) { + if (port->link) + return nw_link_update_stats(port->link); + + return 0; +} diff --git a/src/networkd/port.h b/src/networkd/port.h index 3981c82a..9dcd6c2d 100644 --- a/src/networkd/port.h +++ b/src/networkd/port.h @@ -53,4 +53,9 @@ int nw_port_reconfigure(nw_port* port); int nw_port_has_carrier(nw_port* port); +// Stats +const struct rtnl_link_stats64* nw_port_get_stats64(nw_port* port); +int __nw_port_update_stats(nw_daemon* daemon, nw_port* port, void* data); +int nw_port_update_stats(nw_port* port); + #endif /* NETWORKD_PORT_H */ diff --git a/src/networkd/stats-collector.c b/src/networkd/stats-collector.c new file mode 100644 index 00000000..c10602ec --- /dev/null +++ b/src/networkd/stats-collector.c @@ -0,0 +1,197 @@ +/*############################################################################# +# # +# IPFire.org - A linux based firewall # +# Copyright (C) 2023 IPFire Network 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 "logging.h" +#include "port.h" +#include "stats-collector.h" +#include "zone.h" + +static int __nw_stats_collector_port(nw_daemon* daemon, nw_port* port, void* data) { + return nw_port_update_stats(port); +} + +static int __nw_stats_collector_zone(nw_daemon* daemon, nw_zone* zone, void* data) { + return nw_zone_update_stats(zone); +} + +int nw_stats_collector(sd_event_source* s, long unsigned int usec, void* data) { + nw_daemon* daemon = (nw_daemon*)data; + int r; + + DEBUG("Stats collector has been called\n"); + + // Schedule the next call + r = sd_event_source_set_time(s, usec + NW_STATS_COLLECTOR_INTERVAL); + if (r < 0) + return r; + + // Ports + r = nw_daemon_ports_walk(daemon, __nw_stats_collector_port, NULL); + if (r) + return r; + + // Zones + r = nw_daemon_zones_walk(daemon, __nw_stats_collector_zone, NULL); + if (r) + return r; + + return 0; +} + +static int nw_stats_collector_emit_stats(nw_daemon* daemon, const char* path, + const char* interface, const char* member, const struct rtnl_link_stats64* stats64) { + sd_bus_message* m = NULL; + int r; + + sd_bus* bus = nw_daemon_get_bus(daemon); + + // Allocate a new message + r = sd_bus_message_new_signal(bus, &m, path, interface, member); + if (r < 0) { + errno = -r; + ERROR("Could not allocate bus message: %m\n"); + goto ERROR; + } + + // Open the container + r = sd_bus_message_open_container(m, 'a', "{st}"); + if (r < 0) { + ERROR("Could not open container: %m\n"); + goto ERROR; + } + + const struct stats64_entry { + const char* key; + uint64_t value; + } entries[] = { + { "rx-packets", stats64->rx_packets }, + { "tx-packets", stats64->tx_packets }, + { "rx-bytes", stats64->rx_bytes }, + { "tx-bytes", stats64->tx_bytes }, + { "rx-errors", stats64->rx_errors }, + { "tx-errors", stats64->tx_errors }, + { "rx-dropped", stats64->rx_dropped }, + { "tx-dropped", stats64->tx_dropped }, + { "multicast", stats64->multicast }, + { "collisions", stats64->collisions }, + + // Detailed RX errors + { "rx-length-errors", stats64->rx_length_errors }, + { "rx-over-errors", stats64->rx_over_errors }, + { "rx-crc-errors", stats64->rx_crc_errors }, + { "rx-frame-errors", stats64->rx_frame_errors }, + { "rx-fifo-errors", stats64->rx_fifo_errors }, + { "rx-missed-errors", stats64->rx_missed_errors }, + + // Detailed TX errors + { "tx-aborted-errors", stats64->tx_aborted_errors }, + { "tx-carrier-errors", stats64->tx_carrier_errors }, + { "tx-fifo-errors", stats64->tx_fifo_errors }, + { "tx-heartbeat-errors", stats64->tx_heartbeat_errors }, + { "tx-window-errors", stats64->tx_window_errors }, + + { NULL }, + }; + + for (const struct stats64_entry* e = entries; e->key; e++) { + r = sd_bus_message_append(m, "{st}", e->key, e->value); + if (r < 0) { + ERROR("Could not set stat value: %m\n"); + goto ERROR; + } + } + + // Close the container + r = sd_bus_message_close_container(m); + if (r < 0) { + ERROR("Could not close container: %m\n"); + goto ERROR; + } + + // Emit the signal + r = sd_bus_send(bus, m, NULL); + if (r < 0) { + ERROR("Could not emit the stats signal for %s: %m\n", path); + goto ERROR; + } + +ERROR: + if (m) + sd_bus_message_unref(m); + + return r; +} + +int nw_stats_collector_emit_port_stats(nw_daemon* daemon, nw_port* port) { + const struct rtnl_link_stats64* stats64 = NULL; + char* path = NULL; + int r; + + // Fetch the bus path + path = nw_port_bus_path(port); + + // Fetch the stats + stats64 = nw_port_get_stats64(port); + + // Emit the stats + r = nw_stats_collector_emit_stats(daemon, path, + "org.ipfire.network1.Port", "Stats", stats64); + if (r < 0) { + ERROR("Could not emit stats for port %s: %m\n", nw_port_name(port)); + goto ERROR; + } + +ERROR: + if (path) + free(path); + + return r; +} + +int nw_stats_collector_emit_zone_stats(nw_daemon* daemon, nw_zone* zone) { + const struct rtnl_link_stats64* stats64 = NULL; + char* path = NULL; + int r; + + // Fetch the bus path + path = nw_zone_bus_path(zone); + + // Fetch the stats + stats64 = nw_zone_get_stats64(zone); + + // Emit the stats + r = nw_stats_collector_emit_stats(daemon, path, + "org.ipfire.network1.Zone", "Stats", stats64); + if (r < 0) { + ERROR("Could not emit stats for zone %s: %m\n", nw_zone_name(zone)); + goto ERROR; + } + +ERROR: + if (path) + free(path); + + return r; +} diff --git a/src/networkd/stats-collector.h b/src/networkd/stats-collector.h new file mode 100644 index 00000000..ea11c117 --- /dev/null +++ b/src/networkd/stats-collector.h @@ -0,0 +1,37 @@ +/*############################################################################# +# # +# IPFire.org - A linux based firewall # +# Copyright (C) 2023 IPFire Network 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 NETWORKD_STATS_COLLECTOR_H +#define NETWORKD_STATS_COLLECTOR_H + +#include + +#include "daemon.h" +#include "port.h" +#include "zone.h" + +#define NW_STATS_COLLECTOR_INTERVAL 15 * 1000000ULL // 15 sec in µsec + +int nw_stats_collector(sd_event_source* s, long unsigned int usec, void* data); + +int nw_stats_collector_emit_port_stats(nw_daemon* daemon, nw_port* port); +int nw_stats_collector_emit_zone_stats(nw_daemon* daemon, nw_zone* zone); + +#endif /* NETWORKD_STATS_COLLECTOR_H */ diff --git a/src/networkd/zone.c b/src/networkd/zone.c index 00ab017f..3f47a26d 100644 --- a/src/networkd/zone.c +++ b/src/networkd/zone.c @@ -19,6 +19,7 @@ #############################################################################*/ #include +#include #include #include @@ -27,6 +28,7 @@ #include "daemon.h" #include "link.h" #include "logging.h" +#include "stats-collector.h" #include "string.h" #include "zone.h" @@ -263,3 +265,31 @@ int nw_zone_set_mtu(nw_zone* zone, unsigned int mtu) { return nw_config_set_int(zone->config, "MTU", mtu); } + +/* + Stats +*/ + +const struct rtnl_link_stats64* nw_zone_get_stats64(nw_zone* zone) { + if (!zone->link) + return NULL; + + return nw_link_get_stats64(zone->link); +} + +int __nw_zone_update_stats(nw_daemon* daemon, nw_zone* zone, void* data) { + nw_link* link = (nw_link*)data; + + // Emit stats if link matches + if (zone->link == link) + return nw_stats_collector_emit_zone_stats(daemon, zone); + + return 0; +} + +int nw_zone_update_stats(nw_zone* zone) { + if (zone->link) + return nw_link_update_stats(zone->link); + + return 0; +} diff --git a/src/networkd/zone.h b/src/networkd/zone.h index ad348d71..9737b459 100644 --- a/src/networkd/zone.h +++ b/src/networkd/zone.h @@ -28,6 +28,8 @@ typedef struct nw_zone nw_zone; +#include + #include "daemon.h" int nw_zone_create(nw_zone** zone, nw_daemon* daemon, const char* name); @@ -54,4 +56,8 @@ int nw_zone_has_carrier(nw_zone* zone); unsigned int nw_zone_mtu(nw_zone* zone); int nw_zone_set_mtu(nw_zone* zone, unsigned int mtu); +const struct rtnl_link_stats64* nw_zone_get_stats64(nw_zone* zone); +int __nw_zone_update_stats(nw_daemon* daemon, nw_zone* zone, void* data); +int nw_zone_update_stats(nw_zone* zone); + #endif /* NETWORKD_ZONE_H */ -- 2.47.2