master ohmu/fastfetch-openbsd / src / detection / netio / netio_windows.c
ohmu  ·  2026-10-15
 1#include "netio.h"
 2
 3#include "common/debug.h"
 4#include "common/netif.h"
 5#include "common/windows/unicode.h"
 6
 7#include <ws2tcpip.h>
 8#include <iphlpapi.h>
 9
10static void addInterface(FFlist* result, const FFNetIOOptions* options, NET_IFINDEX ifIndex, bool isDefaultRoute) {
11    MIB_IF_ROW2 ifRow = { .InterfaceIndex = ifIndex };
12    if (!NETIO_SUCCESS(GetIfEntry2(&ifRow))) {
13        return;
14    }
15
16    // localip reports the interfaces that are operational only, so the same filter is applied here
17    if (ifRow.OperStatus != IfOperStatusUp) {
18        return;
19    }
20
21    FF_STRBUF_AUTO_DESTROY name = ffStrbufCreateWS(ifRow.Alias);
22    if (options->namePrefix.length && !ffStrbufStartsWith(&name, &options->namePrefix)) {
23        return;
24    }
25
26    FFNetIOResult* counters = FF_LIST_ADD(FFNetIOResult, *result);
27    *counters = (FFNetIOResult) {
28        .name = ffStrbufCreateMove(&name),
29        .txBytes = ifRow.OutOctets,
30        .rxBytes = ifRow.InOctets,
31        .txPackets = (ifRow.OutUcastPkts + ifRow.OutNUcastPkts),
32        .rxPackets = (ifRow.InUcastPkts + ifRow.InNUcastPkts),
33        .rxErrors = ifRow.InErrors,
34        .txErrors = ifRow.OutErrors,
35        .rxDrops = ifRow.InDiscards,
36        .txDrops = ifRow.OutDiscards,
37        .defaultRoute = isDefaultRoute,
38    };
39}
40
41const char* ffNetIOGetIoCounters(FFlist* result, FFNetIOOptions* options) {
42    uint32_t defaultRouteIfIndex = ffNetifGetDefaultRouteV4()->ifIndex;
43
44    // With `defaultRouteOnly` (the default) the interface carrying the default route is the only one
45    // that can be reported, so it is queried with the single entry API instead of walking a table of
46    // every interface: GetIfEntry2() on one interface costs about 12 us.
47    if (options->defaultRouteOnly) {
48        addInterface(result, options, (NET_IFINDEX) defaultRouteIfIndex, true);
49        return nullptr;
50    }
51
52    // The interface set is taken from the unicast address table, the same source localip uses, so
53    // both modules report the same interfaces. Walking the interface table instead would also report
54    // the NDIS filter layers of every adapter (Npcap, QoS, WFP, ...) and the adapters that carry no
55    // address at all, and those cannot be told apart from real adapters by any MIB_IF_ROW2 field.
56    // GetUnicastIpAddressTable() costs about 72 us, GetIfTable2() about 860 us.
57    PMIB_UNICASTIPADDRESS_TABLE addressTable = nullptr;
58    DWORD ret = GetUnicastIpAddressTable(AF_UNSPEC, &addressTable);
59    if (!NETIO_SUCCESS(ret)) {
60        FF_DEBUG("GetUnicastIpAddressTable() failed: %s", ffDebugWin32Error(ret));
61        return "GetUnicastIpAddressTable() failed";
62    }
63
64    for (ULONG i = 0; i < addressTable->NumEntries; ++i) {
65        NET_IFINDEX ifIndex = addressTable->Table[i].InterfaceIndex;
66
67        // Rows of the same interface are not adjacent (IPv4 rows come first),
68        // so each interface is handled at its first row
69        bool seen = false;
70        for (ULONG j = 0; j < i && !seen; ++j) {
71            seen = addressTable->Table[j].InterfaceIndex == ifIndex;
72        }
73        if (seen) {
74            continue;
75        }
76
77        addInterface(result, options, ifIndex, ifIndex == defaultRouteIfIndex);
78    }
79    FreeMibTable(addressTable);
80
81    return nullptr;
82}