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}