]> git.ipfire.org Git - network.git/commitdiff
networkd: Collect stats regulary and emit them on dbus
authorMichael Tremer <michael.tremer@ipfire.org>
Fri, 14 Apr 2023 17:43:12 +0000 (17:43 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Fri, 14 Apr 2023 17:43:12 +0000 (17:43 +0000)
This is useful for us monitoring interface throughput (e.g. in
collecty).

Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
Makefile.am
src/networkd/daemon.c
src/networkd/daemon.h
src/networkd/link.c
src/networkd/link.h
src/networkd/port.c
src/networkd/port.h
src/networkd/stats-collector.c [new file with mode: 0644]
src/networkd/stats-collector.h [new file with mode: 0644]
src/networkd/zone.c
src/networkd/zone.h

index 80a72365ad6eeb6815e5ce434a1ed1cf06c19b06..22d90d48ee73ca353b0b11005f2cd23dac7c5f2e 100644 (file)
@@ -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 \
index c8e65f4180e2c1505a3a1a62173aaddd03c7f288..749a70b84f172ed9fff31a7a067972703e7d4d91 100644 (file)
@@ -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
 */
index a14d33b229e50c32da254627371aff10f0bea5ae..74e19e692db987388258ef34e524c43e89bdac9e 100644 (file)
@@ -21,6 +21,7 @@
 #ifndef NETWORKD_DAEMON_H
 #define NETWORKD_DAEMON_H
 
+#include <systemd/sd-bus.h>
 #include <systemd/sd-netlink.h>
 
 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
 */
index 09b9a62225d708180ca7baa8f830f4cd57920d4b..1edf20d72594718020a9ae1e5ef9a7772be840fb 100644 (file)
@@ -19,6 +19,7 @@
 #############################################################################*/
 
 #include <linux/if.h>
+#include <linux/if_link.h>
 #include <stddef.h>
 #include <stdlib.h>
 #include <string.h>
@@ -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) {
index 58a825a179cd94dfff5b8f04bb1d88ace50ee543..2bab47c5c90b2be718f3e928aafc4a6f1eae3c5a 100644 (file)
@@ -21,6 +21,8 @@
 #ifndef NETWORKD_LINK_H
 #define NETWORKD_LINK_H
 
+#include <linux/if_link.h>
+
 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);
index 5694024d5838f1a18d3f19ec592737d31c254b9e..7638c7b78446e9cc55e5d87d536a7f90bfdc4601 100644 (file)
@@ -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;
+}
index 3981c82addd47b641ea1e42e710ceecb1e8062d0..9dcd6c2d5dad7324b93622040b67f3c659cfb71d 100644 (file)
@@ -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 (file)
index 0000000..c10602e
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.       #
+#                                                                             #
+#############################################################################*/
+
+#include <stdlib.h>
+
+#include <systemd/sd-bus.h>
+#include <systemd/sd-event.h>
+
+#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 (file)
index 0000000..ea11c11
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.       #
+#                                                                             #
+#############################################################################*/
+
+#ifndef NETWORKD_STATS_COLLECTOR_H
+#define NETWORKD_STATS_COLLECTOR_H
+
+#include <systemd/sd-event.h>
+
+#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 */
index 00ab017f7901617010919b2608f9c48a4d0b4c32..3f47a26d6480fa6de125c14ac2c86d90e96d3b42 100644 (file)
@@ -19,6 +19,7 @@
 #############################################################################*/
 
 #include <limits.h>
+#include <linux/if_link.h>
 #include <stdlib.h>
 
 #include <systemd/sd-bus.h>
@@ -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;
+}
index ad348d7111378928042b5912176f3ecb39674c9a..9737b4591381e862685d6abc35e7c1a33b550d65 100644 (file)
@@ -28,6 +28,8 @@
 
 typedef struct nw_zone nw_zone;
 
+#include <linux/if_link.h>
+
 #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 */