summaryrefslogtreecommitdiffstats
path: root/src/detection/netio
diff options
context:
space:
mode:
authorsumuel <samuel@yakubos.org>2026-08-17 20:44:55 +0000
committersumuel <samuel@yakubos.org>2026-08-17 20:44:55 +0000
commit76424950e373d3b04ac3dd13019151bfba3e8423 (patch)
tree4c3cfdbda039e592b9186be3e28f8d7cfd439e8a /src/detection/netio
Add the files
Diffstat (limited to 'src/detection/netio')
-rw-r--r--src/detection/netio/netio.c81
-rw-r--r--src/detection/netio/netio.h20
-rw-r--r--src/detection/netio/netio_apple.c55
-rw-r--r--src/detection/netio/netio_bsd.c62
-rw-r--r--src/detection/netio/netio_haiku.cpp52
-rw-r--r--src/detection/netio/netio_linux.c91
-rw-r--r--src/detection/netio/netio_nosupport.c5
-rw-r--r--src/detection/netio/netio_sunos.c68
-rw-r--r--src/detection/netio/netio_windows.c71
9 files changed, 505 insertions, 0 deletions
diff --git a/src/detection/netio/netio.c b/src/detection/netio/netio.c
new file mode 100644
index 0000000..c6b9da6
--- /dev/null
+++ b/src/detection/netio/netio.c
@@ -0,0 +1,81 @@
+#include "netio.h"
+
+#include "common/time.h"
+
+static FFlist ioCounters1;
+static uint64_t time1;
+
+void ffPrepareNetIO(FFNetIOOptions* options) {
+ if (options->detectTotal) {
+ return;
+ }
+
+ if (time1 != 0) {
+ return; // Already prepared
+ }
+
+ ffListInit(&ioCounters1);
+ ffNetIOGetIoCounters(&ioCounters1, options);
+ time1 = ffTimeGetNow();
+}
+
+const char* ffDetectNetIO(FFlist* result, FFNetIOOptions* options) {
+ const char* error = NULL;
+
+ if (options->detectTotal) {
+ error = ffNetIOGetIoCounters(result, options);
+ if (error) {
+ return error;
+ }
+ return NULL;
+ }
+
+ if (time1 == 0) {
+ ffListInit(&ioCounters1);
+ error = ffNetIOGetIoCounters(&ioCounters1, options);
+ if (error) {
+ return error;
+ }
+ time1 = ffTimeGetNow();
+ }
+
+ if (ioCounters1.length == 0) {
+ return "No network interfaces found";
+ }
+
+ uint64_t time2 = ffTimeGetNow();
+ while (time2 - time1 < options->waitTime) {
+ ffTimeSleep((uint32_t) (options->waitTime - (time2 - time1)));
+ time2 = ffTimeGetNow();
+ }
+
+ error = ffNetIOGetIoCounters(result, options);
+ if (error) {
+ return error;
+ }
+
+ if (result->length != ioCounters1.length) {
+ return "Different number of network interfaces. Network change?";
+ }
+
+ for (uint32_t i = 0; i < result->length; ++i) {
+ FFNetIOResult* icPrev = FF_LIST_GET(FFNetIOResult, ioCounters1, i);
+ FFNetIOResult* icCurr = FF_LIST_GET(FFNetIOResult, *result, i);
+ if (!ffStrbufEqual(&icPrev->name, &icCurr->name)) {
+ return "Network interface name changed";
+ }
+
+ static_assert(sizeof(FFNetIOResult) - offsetof(FFNetIOResult, txBytes) == sizeof(uint64_t) * 8, "Unexpected struct FFNetIOResult layout");
+ for (size_t off = offsetof(FFNetIOResult, txBytes); off < sizeof(FFNetIOResult); off += sizeof(uint64_t)) {
+ uint64_t* prevValue = (uint64_t*) ((uint8_t*) icPrev + off);
+ uint64_t* currValue = (uint64_t*) ((uint8_t*) icCurr + off);
+ uint64_t temp = *currValue;
+ *currValue -= *prevValue;
+ *currValue /= (time2 - time1) / 1000 /* seconds */;
+ *prevValue = temp;
+ }
+ }
+ time1 = time2;
+
+ return NULL;
+}
diff --git a/src/detection/netio/netio.h b/src/detection/netio/netio.h
new file mode 100644
index 0000000..1e48b53
--- /dev/null
+++ b/src/detection/netio/netio.h
@@ -0,0 +1,20 @@
+#pragma once
+
+#include "fastfetch.h"
+#include "modules/netio/option.h"
+
+typedef struct FFNetIOResult {
+ FFstrbuf name;
+ bool defaultRoute;
+ uint64_t txBytes;
+ uint64_t rxBytes;
+ uint64_t txPackets;
+ uint64_t rxPackets;
+ uint64_t rxErrors;
+ uint64_t txErrors;
+ uint64_t rxDrops;
+ uint64_t txDrops;
+} FFNetIOResult;
+
+const char* ffDetectNetIO(FFlist* result, FFNetIOOptions* options);
+const char* ffNetIOGetIoCounters(FFlist* result, FFNetIOOptions* options);
diff --git a/src/detection/netio/netio_apple.c b/src/detection/netio/netio_apple.c
new file mode 100644
index 0000000..125cfd9
--- /dev/null
+++ b/src/detection/netio/netio_apple.c
@@ -0,0 +1,55 @@
+#include "netio.h"
+
+#include "common/netif.h"
+#include "common/mallocHelper.h"
+
+#include <net/if.h>
+#include <net/if_mib.h>
+#include <sys/sysctl.h>
+
+const char* ffNetIOGetIoCounters(FFlist* result, FFNetIOOptions* options) {
+ int mib[] = { CTL_NET, PF_LINK, NETLINK_GENERIC, options->defaultRouteOnly ? IFMIB_IFDATA : IFMIB_IFALLDATA, options->defaultRouteOnly ? (int) ffNetifGetDefaultRouteV4()->ifIndex : 0, IFDATA_GENERAL };
+
+ size_t bufSize = 0;
+ if (sysctl(mib, ARRAY_SIZE(mib), NULL, &bufSize, 0, 0) < 0) {
+ return "sysctl(mib, ARRAY_SIZE(mib), NULL, &bufSize, 0, 0) failed";
+ }
+
+ assert(bufSize % sizeof(struct ifmibdata) == 0);
+
+ FF_AUTO_FREE struct ifmibdata* buf = (struct ifmibdata*) malloc(bufSize);
+ if (sysctl(mib, ARRAY_SIZE(mib), buf, &bufSize, 0, 0) < 0) {
+ return "sysctl(mib, ARRAY_SIZE(mib), buf, &bufSize, 0, 0) failed";
+ }
+
+ size_t ifCount = bufSize / sizeof(struct ifmibdata);
+
+ const char* defaultRouteIfName = ffNetifGetDefaultRouteV4()->ifName;
+
+ for (size_t i = 0; i < ifCount; i++) {
+ struct ifmibdata* mibdata = &buf[i];
+ if (!(mibdata->ifmd_flags & IFF_RUNNING) || (mibdata->ifmd_flags & IFF_NOARP)) {
+ continue;
+ }
+
+ if (options->namePrefix.length && strncmp(mibdata->ifmd_name, options->namePrefix.chars, options->namePrefix.length) != 0) {
+ continue;
+ }
+
+ FFNetIOResult* counters = FF_LIST_ADD(FFNetIOResult, *result);
+ *counters = (FFNetIOResult) {
+ .name = ffStrbufCreateS(mibdata->ifmd_name),
+ .txBytes = mibdata->ifmd_data.ifi_obytes,
+ .rxBytes = mibdata->ifmd_data.ifi_ibytes,
+ .txPackets = mibdata->ifmd_data.ifi_opackets,
+ .rxPackets = mibdata->ifmd_data.ifi_ipackets,
+ .txErrors = mibdata->ifmd_data.ifi_oerrors,
+ .rxErrors = mibdata->ifmd_data.ifi_ierrors,
+ .txDrops = mibdata->ifmd_snd_drops,
+ .rxDrops = mibdata->ifmd_data.ifi_iqdrops,
+ .defaultRoute = strncmp(mibdata->ifmd_name, defaultRouteIfName, IFNAMSIZ) == 0,
+ };
+ }
+
+ return NULL;
+}
diff --git a/src/detection/netio/netio_bsd.c b/src/detection/netio/netio_bsd.c
new file mode 100644
index 0000000..83c1b6b
--- /dev/null
+++ b/src/detection/netio/netio_bsd.c
@@ -0,0 +1,62 @@
+#include "netio.h"
+
+#include "common/netif.h"
+#include "common/mallocHelper.h"
+
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/if_types.h>
+#include <net/route.h>
+#include <sys/sysctl.h>
+#include <sys/socket.h>
+
+const char* ffNetIOGetIoCounters(FFlist* result, FFNetIOOptions* options) {
+ uint32_t defaultRouteIfIndex = ffNetifGetDefaultRouteV4()->ifIndex;
+
+ size_t bufSize = 0;
+ if (sysctl((int[]) { CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST, (options->defaultRouteOnly ? (int) defaultRouteIfIndex : 0) }, 6, NULL, &bufSize, 0, 0) < 0) {
+ return "sysctl({ CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST, ifIndex }, 6, NULL, &bufSize, 0, 0) failed";
+ }
+
+ FF_AUTO_FREE struct if_msghdr* buf = (struct if_msghdr*) malloc(bufSize);
+ if (sysctl((int[]) { CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST, (options->defaultRouteOnly ? (int) defaultRouteIfIndex : 0) }, 6, buf, &bufSize, 0, 0) < 0) {
+ return "sysctl({ CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST, ifIndex }, 6, buf, &bufSize, 0, 0) failed";
+ }
+
+ for (struct if_msghdr* ifm = buf;
+ ifm < (struct if_msghdr*) ((uint8_t*) buf + bufSize);
+ ifm = (struct if_msghdr*) ((uint8_t*) ifm + ifm->ifm_msglen)) {
+ if (ifm->ifm_type != RTM_IFINFO || !(ifm->ifm_flags & IFF_RUNNING) || (ifm->ifm_flags & IFF_NOARP)) {
+ continue;
+ }
+
+ struct sockaddr_dl* sdl = (struct sockaddr_dl*) (ifm + 1);
+ assert(sdl->sdl_family == AF_LINK);
+
+ sdl->sdl_data[sdl->sdl_nlen] = 0;
+
+ if (options->namePrefix.length && strncmp(sdl->sdl_data, options->namePrefix.chars, options->namePrefix.length) != 0) {
+ continue;
+ }
+
+ FFNetIOResult* counters = FF_LIST_ADD(FFNetIOResult, *result);
+ *counters = (FFNetIOResult) {
+ .name = ffStrbufCreateNS(sdl->sdl_nlen, sdl->sdl_data),
+ .txBytes = ifm->ifm_data.ifi_obytes,
+ .rxBytes = ifm->ifm_data.ifi_ibytes,
+ .txPackets = ifm->ifm_data.ifi_opackets,
+ .rxPackets = ifm->ifm_data.ifi_ipackets,
+ .txErrors = ifm->ifm_data.ifi_oerrors,
+ .rxErrors = ifm->ifm_data.ifi_ierrors,
+#ifdef FF_HAVE_IFI_OQDROPS
+ .txDrops = ifm->ifm_data.ifi_oqdrops,
+#else
+ .txDrops = 0, // unsupported
+#endif
+ .rxDrops = ifm->ifm_data.ifi_iqdrops,
+ .defaultRoute = sdl->sdl_index == defaultRouteIfIndex,
+ };
+ }
+
+ return NULL;
+}
diff --git a/src/detection/netio/netio_haiku.cpp b/src/detection/netio/netio_haiku.cpp
new file mode 100644
index 0000000..88a123d
--- /dev/null
+++ b/src/detection/netio/netio_haiku.cpp
@@ -0,0 +1,52 @@
+extern "C" {
+#include "netio.h"
+#include "common/netif.h"
+}
+
+#include <NetworkInterface.h>
+#include <NetworkRoster.h>
+
+const char* ffNetIOGetIoCounters(FFlist* result, FFNetIOOptions* options) {
+ BNetworkRoster& roster = BNetworkRoster::Default();
+
+ BNetworkInterface interface;
+ uint32 cookie = 0;
+
+ uint32_t defaultRouteIfIndex = ffNetifGetDefaultRouteV4()->ifIndex;
+
+ while (roster.GetNextInterface(&cookie, interface) == B_OK) {
+ if (!interface.Exists()) {
+ continue;
+ }
+
+ bool defaultRoute = interface.Index() == defaultRouteIfIndex;
+ if (options->defaultRouteOnly && !defaultRoute) {
+ continue;
+ }
+
+ if (options->namePrefix.length && strncmp(interface.Name(), options->namePrefix.chars, options->namePrefix.length) != 0) {
+ continue;
+ }
+
+ ifreq_stats stats = {};
+ if (interface.GetStats(stats) != B_OK) {
+ continue;
+ }
+
+ FFNetIOResult* counters = FF_LIST_ADD(FFNetIOResult, *result);
+ *counters = (FFNetIOResult) {
+ .name = ffStrbufCreateS(interface.Name()),
+ .defaultRoute = defaultRoute,
+ .txBytes = stats.send.bytes,
+ .rxBytes = stats.receive.bytes,
+ .txPackets = stats.send.packets,
+ .rxPackets = stats.receive.packets,
+ .rxErrors = stats.receive.errors,
+ .txErrors = stats.send.errors,
+ .rxDrops = stats.receive.dropped,
+ .txDrops = stats.send.dropped
+ };
+ }
+
+ return NULL;
+}
diff --git a/src/detection/netio/netio_linux.c b/src/detection/netio/netio_linux.c
new file mode 100644
index 0000000..5870d3f
--- /dev/null
+++ b/src/detection/netio/netio_linux.c
@@ -0,0 +1,91 @@
+#include "netio.h"
+
+#include "common/io.h"
+#include "common/netif.h"
+#include "common/strutil.h"
+
+#include <fcntl.h>
+#include <net/if.h>
+
+static void getData(FFstrbuf* buffer, const char* ifName, bool isDefaultRoute, int basefd, FFlist* result) {
+ FF_AUTO_CLOSE_FD int dfd = openat(basefd, ifName, O_RDONLY | O_DIRECTORY);
+ if (dfd < 0) {
+ return;
+ }
+
+ char operstate;
+ if (!ffReadFileDataRelative(dfd, "operstate", 1, &operstate) || operstate != 'u' /* up or unknown */) {
+ return;
+ }
+
+ FFNetIOResult* counters = FF_LIST_ADD(FFNetIOResult, *result);
+ ffStrbufInitS(&counters->name, ifName);
+ counters->defaultRoute = isDefaultRoute;
+
+ if (ffReadFileBufferRelative(dfd, "statistics/rx_bytes", buffer)) {
+ counters->rxBytes = ffStrbufToUInt(buffer, 0);
+ }
+
+ if (ffReadFileBufferRelative(dfd, "statistics/tx_bytes", buffer)) {
+ counters->txBytes = ffStrbufToUInt(buffer, 0);
+ }
+
+ if (ffReadFileBufferRelative(dfd, "statistics/rx_packets", buffer)) {
+ counters->rxPackets = ffStrbufToUInt(buffer, 0);
+ }
+
+ if (ffReadFileBufferRelative(dfd, "statistics/tx_packets", buffer)) {
+ counters->txPackets = ffStrbufToUInt(buffer, 0);
+ }
+
+ if (ffReadFileBufferRelative(dfd, "statistics/rx_errors", buffer)) {
+ counters->rxErrors = ffStrbufToUInt(buffer, 0);
+ }
+
+ if (ffReadFileBufferRelative(dfd, "statistics/tx_errors", buffer)) {
+ counters->txErrors = ffStrbufToUInt(buffer, 0);
+ }
+
+ if (ffReadFileBufferRelative(dfd, "statistics/rx_dropped", buffer)) {
+ counters->rxDrops = ffStrbufToUInt(buffer, 0);
+ }
+
+ if (ffReadFileBufferRelative(dfd, "statistics/tx_dropped", buffer)) {
+ counters->txDrops = ffStrbufToUInt(buffer, 0);
+ }
+}
+
+const char* ffNetIOGetIoCounters(FFlist* result, FFNetIOOptions* options) {
+ FF_AUTO_CLOSE_DIR DIR* dirp = opendir("/sys/class/net");
+ if (!dirp) {
+ return "opendir(\"/sys/class/net\") == NULL";
+ }
+
+ FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
+
+ const char* defaultRouteIfName = ffNetifGetDefaultRouteV4()->ifName;
+
+ if (options->defaultRouteOnly) {
+ if (options->namePrefix.length && strncmp(defaultRouteIfName, options->namePrefix.chars, options->namePrefix.length) != 0) {
+ return NULL;
+ }
+
+ getData(&buffer, defaultRouteIfName, true, dirfd(dirp), result);
+ } else {
+ struct dirent* entry;
+ while ((entry = readdir(dirp)) != NULL) {
+ const char* ifName = entry->d_name;
+ if (ifName[0] == '.') {
+ continue;
+ }
+
+ if (options->namePrefix.length && strncmp(ifName, options->namePrefix.chars, options->namePrefix.length) != 0) {
+ continue;
+ }
+
+ getData(&buffer, ifName, ffStrEquals(ifName, defaultRouteIfName), dirfd(dirp), result);
+ }
+ }
+
+ return NULL;
+}
diff --git a/src/detection/netio/netio_nosupport.c b/src/detection/netio/netio_nosupport.c
new file mode 100644
index 0000000..30f73a7
--- /dev/null
+++ b/src/detection/netio/netio_nosupport.c
@@ -0,0 +1,5 @@
+#include "netio.h"
+
+const char* ffNetIOGetIoCounters(FF_A_UNUSED FFlist* result, FF_A_UNUSED FFNetIOOptions* options) {
+ return "Not supported on this platform";
+}
diff --git a/src/detection/netio/netio_sunos.c b/src/detection/netio/netio_sunos.c
new file mode 100644
index 0000000..3789e45
--- /dev/null
+++ b/src/detection/netio/netio_sunos.c
@@ -0,0 +1,68 @@
+#include "netio.h"
+#include "common/netif.h"
+#include "common/strutil.h"
+
+#include <kstat.h>
+
+static inline void kstatFreeWrap(kstat_ctl_t** pkc) {
+ assert(pkc);
+ if (*pkc) {
+ kstat_close(*pkc);
+ }
+}
+
+const char* ffNetIOGetIoCounters(FFlist* result, FFNetIOOptions* options) {
+ FF_A_CLEANUP(kstatFreeWrap) kstat_ctl_t* kc = kstat_open();
+ if (!kc) {
+ return "kstat_open() failed";
+ }
+
+ const char* defaultRouteIfName = ffNetifGetDefaultRouteV4()->ifName;
+
+ for (kstat_t* ks = kc->kc_chain; ks; ks = ks->ks_next) {
+ if (!ffStrEquals(ks->ks_class, "net") || !ffStrEquals(ks->ks_module, "link")) {
+ continue;
+ }
+
+ if (options->namePrefix.length && strncmp(ks->ks_name, options->namePrefix.chars, options->namePrefix.length) != 0) {
+ continue;
+ }
+
+ bool isDefaultRoute = ffStrEquals(ks->ks_name, defaultRouteIfName);
+ if (options->defaultRouteOnly && !isDefaultRoute) {
+ continue;
+ }
+
+ if (kstat_read(kc, ks, NULL) < 0) {
+ continue;
+ }
+
+ FFNetIOResult* counters = FF_LIST_ADD(FFNetIOResult, *result);
+
+ kstat_named_t* wbytes = (kstat_named_t*) kstat_data_lookup(ks, "obytes64");
+ kstat_named_t* rbytes = (kstat_named_t*) kstat_data_lookup(ks, "rbytes64");
+ kstat_named_t* wpkts = (kstat_named_t*) kstat_data_lookup(ks, "opackets64");
+ kstat_named_t* rpkts = (kstat_named_t*) kstat_data_lookup(ks, "ipackets64");
+ kstat_named_t* werrs = (kstat_named_t*) kstat_data_lookup(ks, "oerrors");
+ kstat_named_t* rerrs = (kstat_named_t*) kstat_data_lookup(ks, "ierrors");
+
+ *counters = (FFNetIOResult) {
+ .name = ffStrbufCreateS(ks->ks_name),
+ .txBytes = wbytes->value.ui64,
+ .rxBytes = rbytes->value.ui64,
+ .txPackets = wpkts->value.ui64,
+ .rxPackets = rpkts->value.ui64,
+ .txErrors = werrs->value.ui64,
+ .rxErrors = rerrs->value.ui64,
+ .txDrops = 0, // unsupported
+ .rxDrops = 0,
+ .defaultRoute = isDefaultRoute,
+ };
+
+ if (options->defaultRouteOnly) {
+ break;
+ }
+ }
+
+ return NULL;
+}
diff --git a/src/detection/netio/netio_windows.c b/src/detection/netio/netio_windows.c
new file mode 100644
index 0000000..3c64334
--- /dev/null
+++ b/src/detection/netio/netio_windows.c
@@ -0,0 +1,71 @@
+#include "netio.h"
+
+#include "common/netif.h"
+#include "common/mallocHelper.h"
+#include "common/windows/unicode.h"
+
+#include <ws2tcpip.h>
+#include <iphlpapi.h>
+
+const char* ffNetIOGetIoCounters(FFlist* result, FFNetIOOptions* options) {
+ IP_ADAPTER_ADDRESSES* FF_AUTO_FREE adapter_addresses = NULL;
+
+ // Multiple attempts in case interfaces change while
+ // we are in the middle of querying them.
+ DWORD adapter_addresses_buffer_size = 0;
+ for (int attempts = 0;; ++attempts) {
+ if (adapter_addresses_buffer_size) {
+ adapter_addresses = (IP_ADAPTER_ADDRESSES*) realloc(adapter_addresses, adapter_addresses_buffer_size);
+ assert(adapter_addresses);
+ }
+
+ DWORD error = GetAdaptersAddresses(
+ AF_UNSPEC,
+ GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER,
+ NULL,
+ adapter_addresses,
+ &adapter_addresses_buffer_size);
+
+ if (error == ERROR_SUCCESS) {
+ break;
+ } else if (ERROR_BUFFER_OVERFLOW == error && attempts < 4) {
+ continue;
+ } else {
+ return "GetAdaptersAddresses() failed";
+ }
+ }
+
+ uint32_t defaultRouteIfIndex = ffNetifGetDefaultRouteV4()->ifIndex;
+
+ // Iterate through all of the adapters
+ for (IP_ADAPTER_ADDRESSES* adapter = adapter_addresses; adapter; adapter = adapter->Next) {
+ bool isDefaultRoute = adapter->IfIndex == defaultRouteIfIndex;
+ if (options->defaultRouteOnly && !isDefaultRoute) {
+ continue;
+ }
+
+ FF_STRBUF_AUTO_DESTROY name = ffStrbufCreateWS(adapter->FriendlyName);
+ if (options->namePrefix.length && !ffStrbufStartsWith(&name, &options->namePrefix)) {
+ continue;
+ }
+
+ MIB_IF_ROW2 ifRow = { .InterfaceIndex = adapter->IfIndex };
+ if (GetIfEntry2(&ifRow) == NO_ERROR) {
+ FFNetIOResult* counters = FF_LIST_ADD(FFNetIOResult, *result);
+ *counters = (FFNetIOResult) {
+ .name = ffStrbufCreateMove(&name),
+ .txBytes = ifRow.OutOctets,
+ .rxBytes = ifRow.InOctets,
+ .txPackets = (ifRow.OutUcastPkts + ifRow.OutNUcastPkts),
+ .rxPackets = (ifRow.InUcastPkts + ifRow.InNUcastPkts),
+ .rxErrors = ifRow.InErrors,
+ .txErrors = ifRow.OutErrors,
+ .rxDrops = ifRow.InDiscards,
+ .txDrops = ifRow.OutDiscards,
+ .defaultRoute = isDefaultRoute,
+ };
+ }
+ }
+
+ return NULL;
+}