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 \
#include "links.h"
#include "logging.h"
#include "ports.h"
+#include "stats-collector.h"
#include "zone.h"
#include "zones.h"
// 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,
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;
if (r)
return r;
+ // Start the stats collector
+ r = nw_daemon_starts_stats_collector(daemon);
+ if (r)
+ return r;
+
return 0;
}
// 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)
return 0;
}
+/*
+ Bus
+*/
+sd_bus* nw_daemon_get_bus(nw_daemon* daemon) {
+ return daemon->bus;
+}
+
/*
Netlink
*/
#ifndef NETWORKD_DAEMON_H
#define NETWORKD_DAEMON_H
+#include <systemd/sd-bus.h>
#include <systemd/sd-netlink.h>
typedef struct nw_daemon nw_daemon;
int nw_daemon_save(nw_daemon* daemon);
+/*
+ Bus
+*/
+sd_bus* nw_daemon_get_bus(nw_daemon* daemon);
+
/*
Netlink
*/
#############################################################################*/
#include <linux/if.h>
+#include <linux/if_link.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
NW_LINK_DESTROYED,
} state;
+ // Stats
+ struct rtnl_link_stats64 stats64;
+
// MTU
uint32_t mtu;
uint32_t min_mtu;
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) {
#ifndef NETWORKD_LINK_H
#define NETWORKD_LINK_H
+#include <linux/if_link.h>
+
typedef struct nw_link nw_link;
#include "daemon.h"
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);
#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;
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;
+}
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 */
--- /dev/null
+/*#############################################################################
+# #
+# 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;
+}
--- /dev/null
+/*#############################################################################
+# #
+# 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 */
#############################################################################*/
#include <limits.h>
+#include <linux/if_link.h>
#include <stdlib.h>
#include <systemd/sd-bus.h>
#include "daemon.h"
#include "link.h"
#include "logging.h"
+#include "stats-collector.h"
#include "string.h"
#include "zone.h"
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;
+}
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);
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 */