diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f9cbe6bb..dac76a7ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +# Unreleased + +Changes: +* LocalIP flags on Windows now describe the interface itself — its operational status, its media connection state and its type, for example `ETHERNET_CSMACD`, `IEEE80211` or `SOFTWARE_LOOPBACK` — instead of the `IP_ADAPTER_*` bits. + +Features: +* Improved performance of LocalIP, NetIO and DNS on Windows + # 2.69.0 Changes: diff --git a/src/detection/dns/dns_windows.c b/src/detection/dns/dns_windows.c index edab21e86..ac88042fa 100644 --- a/src/detection/dns/dns_windows.c +++ b/src/detection/dns/dns_windows.c @@ -1,67 +1,241 @@ #include "detection/dns/dns.h" #include "common/netif.h" -#include "common/mallocHelper.h" +#include "common/debug.h" +#include "common/windows/registry.h" #include #include +// The resolvers of an interface are stored in the registry, under a key named after its GUID: +// HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\{GUID} IPv4 +// HKLM\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters\Interfaces\{GUID} IPv6 +// A statically configured `NameServer` takes precedence over the value handed out by DHCP, which is +// the precedence the IP stack applies as well - verified against GetAdaptersAddresses() on a machine +// with both set, where only the static list was reported. +// +// This replaces GetAdaptersAddresses(), which builds the unicast, anycast, multicast and DNS server +// lists of every adapter - about 9 ms on a machine with 52 of them - even though the module only +// reports the resolvers of the interface that carries the default route. Reading the one key that +// matters costs about 16 us (12 us to open it, 4 us to read the value, see +// src/common/windows/registry.c), and the module as a whole goes from about 11 ms to about 0.3 ms. +// +// Known limitation: a resolver learned from a router advertisement (RDNSS) is not written to the +// registry by any Windows version, so on a SLAAC-only network (no DHCPv6 lease, no static +// `NameServer`) IPv6 resolvers are not reported at all. GetAdaptersAddresses() reported them. +#define FF_DNS_REGISTRY_KEY_V4 L"SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces\\" +#define FF_DNS_REGISTRY_KEY_V6 L"SYSTEM\\CurrentControlSet\\Services\\Tcpip6\\Parameters\\Interfaces\\" + +// Enough for either key path plus the 38 characters of a GUID +#define FF_DNS_REGISTRY_PATH_SIZE 256 + +static void appendAddress(FFlist* results, FFstrbuf* servers, uint32_t start, uint32_t end, FFDNSShowType showType) { + // A link local address may carry a zone index, which the reported address never includes + char address[INET6_ADDRSTRLEN]; + uint32_t length = end - start; + if (length >= sizeof(address)) { + length = sizeof(address) - 1; + } + + uint32_t i = 0; + for (; i < length && servers->chars[start + i] != '%'; ++i) { + address[i] = servers->chars[start + i]; + } + address[i] = '\0'; + + if (showType & FF_DNS_TYPE_IPV4_BIT) { + IN_ADDR ipv4; + if (InetPtonA(AF_INET, address, &ipv4) == 1) { + FFstrbuf* item = FF_LIST_ADD(FFstrbuf, *results); + ffStrbufInitA(item, INET_ADDRSTRLEN); + item->length = (uint32_t) (RtlIpv4AddressToStringA(&ipv4, item->chars) - item->chars); + return; + } + } + + if (showType & FF_DNS_TYPE_IPV6_BIT) { + IN6_ADDR ipv6; + if (InetPtonA(AF_INET6, address, &ipv6) == 1) { + FFstrbuf* item = FF_LIST_ADD(FFstrbuf, *results); + ffStrbufInitA(item, INET6_ADDRSTRLEN); + item->length = (uint32_t) (RtlIpv6AddressToStringA(&ipv6, item->chars) - item->chars); + } + } +} + +static void appendAddressList(FFstrbuf* servers, FFDNSShowType showType, FFlist* results) { + uint32_t start = 0; + while (start < servers->length) { + while (start < servers->length && (servers->chars[start] == ',' || servers->chars[start] == ' ' || servers->chars[start] == '\t')) { + ++start; + } + + uint32_t end = start; + while (end < servers->length && servers->chars[end] != ',' && servers->chars[end] != ' ' && servers->chars[end] != '\t') { + ++end; + } + + if (end > start) { + appendAddress(results, servers, start, end, showType); + } + start = end; + } +} + +// A DHCPv6 lease stores its resolvers as a plain list of 16 byte addresses +static void appendAddressArray(const BYTE* data, uint32_t dataLength, FFDNSShowType showType, FFlist* results) { + if (!(showType & FF_DNS_TYPE_IPV6_BIT)) { + return; + } + + for (uint32_t offset = 0; offset + sizeof(IN6_ADDR) <= dataLength; offset += sizeof(IN6_ADDR)) { + IN6_ADDR ipv6; + memcpy(&ipv6, data + offset, sizeof(ipv6)); + + FFstrbuf* item = FF_LIST_ADD(FFstrbuf, *results); + ffStrbufInitA(item, INET6_ADDRSTRLEN); + item->length = (uint32_t) (RtlIpv6AddressToStringA(&ipv6, item->chars) - item->chars); + } +} + +// `Dhcpv6InterfaceOptions` stores the DHCPv6 options of the last lease as an array of records: a 20 +// byte header (option code, 0, payload length, 0, 0xFFFFFFFF) followed by the payload, padded to 16 +// bytes. Option 23 is OPTION_DNS_SERVERS (RFC 3646). +static void appendDhcpv6Options(const BYTE* data, uint32_t dataLength, FFDNSShowType showType, FFlist* results) { + for (uint32_t offset = 0; offset + 20 <= dataLength;) { + uint32_t code, length; + memcpy(&code, data + offset, sizeof(code)); + memcpy(&length, data + offset + 8, sizeof(length)); + + uint32_t payloadLength = dataLength - (offset + 20); + if (payloadLength > length) { + payloadLength = length; + } + + if (code == 23) { + appendAddressArray(data + offset + 20, payloadLength, showType, results); + return; + } + + if (payloadLength < length) { + return; + } + offset += 20 + ((payloadLength + 15) & ~(uint32_t) 15); + } +} + +static void detectV4(const wchar_t* guid, FFDNSOptions* options, FFlist* results) { + wchar_t keyPath[FF_DNS_REGISTRY_PATH_SIZE] = FF_DNS_REGISTRY_KEY_V4; + wcscat(keyPath, guid); + FF_DEBUG("v4 key '%ls'", keyPath); + + FF_AUTO_CLOSE_FD HANDLE hKey = nullptr; + if (!ffRegOpenKeyForRead(HKEY_LOCAL_MACHINE, keyPath, &hKey, nullptr)) { + FF_DEBUG("v4 key open failed"); + return; + } + + FF_STRBUF_AUTO_DESTROY servers = ffStrbufCreate(); + // `NameServer` holds the statically configured list and wins over the one handed out by DHCP. A + // value that exists but holds no address must not shadow the fallback, hence the length checks. + bool got = (ffRegReadStrbuf(hKey, L"NameServer", &servers, nullptr) && servers.length) || + (ffRegReadStrbuf(hKey, L"DhcpNameServer", &servers, nullptr) && servers.length); + FF_DEBUG("v4 got=%d servers='%s'", got, servers.chars); + if (!got) { + return; + } + + appendAddressList(&servers, options->showType, results); + FF_DEBUG("v4 results=%u", results->length); +} + +static void detectV6(const wchar_t* guid, FFDNSOptions* options, FFlist* results) { + wchar_t keyPath[FF_DNS_REGISTRY_PATH_SIZE] = FF_DNS_REGISTRY_KEY_V6; + wcscat(keyPath, guid); + FF_DEBUG("v6 key '%ls'", keyPath); + + FF_AUTO_CLOSE_FD HANDLE hKey = nullptr; + if (!ffRegOpenKeyForRead(HKEY_LOCAL_MACHINE, keyPath, &hKey, nullptr)) { + FF_DEBUG("v6 key open failed"); + return; + } + + FF_STRBUF_AUTO_DESTROY servers = ffStrbufCreate(); + if (ffRegReadStrbuf(hKey, L"NameServer", &servers, nullptr) && servers.length) { + FF_DEBUG("v6 servers='%s'", servers.chars); + appendAddressList(&servers, options->showType, results); + FF_DEBUG("v6 results=%u", results->length); + return; + } + + // The two DHCPv6 values below are documented as REG_BINARY and are always written that way; the + // registry reader reports the bytes without their type, so the parsing below trusts that. + BYTE data[4096]; + + FFArgBuffer dhcpv6Servers = { .data = data, .length = sizeof(data) }; + if (ffRegReadData(hKey, L"Dhcpv6DNSServers", &dhcpv6Servers, nullptr)) { + FF_DEBUG("v6 Dhcpv6DNSServers cb=%u", dhcpv6Servers.length); + appendAddressArray(data, dhcpv6Servers.length, options->showType, results); + return; + } + + FFArgBuffer dhcpv6Options = { .data = data, .length = sizeof(data) }; + if (ffRegReadData(hKey, L"Dhcpv6InterfaceOptions", &dhcpv6Options, nullptr)) { + FF_DEBUG("v6 Dhcpv6InterfaceOptions cb=%u", dhcpv6Options.length); + appendDhcpv6Options(data, dhcpv6Options.length, options->showType, results); + return; + } + + FF_DEBUG("v6 no resolver"); +} + const char* ffDetectDNS(FFDNSOptions* options, FFlist* results) { - FF_AUTO_FREE IP_ADAPTER_ADDRESSES* adapter_addresses = nullptr; - - // 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( - options->showType & FF_DNS_TYPE_IPV4_BIT - ? options->showType & FF_DNS_TYPE_IPV6_BIT ? AF_UNSPEC : AF_INET - : AF_INET6, - GAA_FLAG_SKIP_UNICAST | GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_FRIENDLY_NAME, - nullptr, - adapter_addresses, - &adapter_addresses_buffer_size); - - if (error == ERROR_SUCCESS) { - break; - } else if (ERROR_BUFFER_OVERFLOW == error && attempts < 4) { - continue; - } else { - return "GetAdaptersAddresses() failed"; - } + FF_DEBUG("enter, showType=%u", (unsigned) options->showType); + // The interface that carries the default route is the one the IP stack resolves names through. + // ffNetifGetDefaultRouteV4() already ignores the interfaces that are not operational. + NET_IFINDEX ifIndex = (NET_IFINDEX) ffNetifGetDefaultRouteV4()->ifIndex; + FF_DEBUG("ifIndex=%u", (unsigned) ifIndex); + if (!ifIndex) { + ifIndex = (NET_IFINDEX) ffNetifGetDefaultRouteV6()->ifIndex; + } + if (!ifIndex) { + return "Failed to detect default route"; } - uint32_t defaultRouteIfIndex = ffNetifGetDefaultRouteV4()->ifIndex; - // Iterate through all of the adapters - for (IP_ADAPTER_ADDRESSES* adapter = adapter_addresses; adapter; adapter = adapter->Next) { - if (adapter->IfIndex != defaultRouteIfIndex) { - continue; - } - if (adapter->OperStatus != IfOperStatusUp) { - continue; - } - - for (IP_ADAPTER_DNS_SERVER_ADDRESS_XP* ifa = adapter->FirstDnsServerAddress; ifa; ifa = ifa->Next) { - // The element must only be added once it can be filled: `FF_LIST_ADD` does not zero the - // new element, so an unrecognized address family used to leave an uninitialized FFstrbuf - // in the list, which the callers then read and free - if (ifa->Address.lpSockaddr->sa_family == AF_INET) { - SOCKADDR_IN* ipv4 = (SOCKADDR_IN*) ifa->Address.lpSockaddr; - FFstrbuf* item = FF_LIST_ADD(FFstrbuf, *results); - ffStrbufInitA(item, INET_ADDRSTRLEN); - item->length = (uint32_t) (RtlIpv4AddressToStringA(&ipv4->sin_addr, item->chars) - item->chars); - } else if (ifa->Address.lpSockaddr->sa_family == AF_INET6) { - SOCKADDR_IN6* ipv6 = (SOCKADDR_IN6*) ifa->Address.lpSockaddr; - FFstrbuf* item = FF_LIST_ADD(FFstrbuf, *results); - ffStrbufInitA(item, INET6_ADDRSTRLEN); - item->length = (uint32_t) (RtlIpv6AddressToStringA(&ipv6->sin6_addr, item->chars) - item->chars); - } - } - break; + NET_LUID luid; + if (ConvertInterfaceIndexToLuid(ifIndex, &luid) != NO_ERROR) { + return "ConvertInterfaceIndexToLuid() failed"; } + + GUID guid; + if (ConvertInterfaceLuidToGuid(&luid, &guid) != NO_ERROR) { + return "ConvertInterfaceLuidToGuid() failed"; + } + + wchar_t guidStr[64]; + // clang-format off + swprintf(guidStr, ARRAY_SIZE(guidStr), L"{%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}", + (unsigned long) guid.Data1, + (unsigned int) guid.Data2, + (unsigned int) guid.Data3, + (unsigned int) guid.Data4[0], + (unsigned int) guid.Data4[1], + (unsigned int) guid.Data4[2], + (unsigned int) guid.Data4[3], + (unsigned int) guid.Data4[4], + (unsigned int) guid.Data4[5], + (unsigned int) guid.Data4[6], + (unsigned int) guid.Data4[7]); + // clang-format on + + // IPv4 first, so the list is grouped by address family the same way the printed output is + if (options->showType & FF_DNS_TYPE_IPV4_BIT) { + detectV4(guidStr, options, results); + } + + if (options->showType & FF_DNS_TYPE_IPV6_BIT) { + detectV6(guidStr, options, results); + } + + FF_DEBUG("return, results=%u", results->length); return nullptr; } diff --git a/src/detection/localip/localip_windows.c b/src/detection/localip/localip_windows.c index c36118015..78c8a0f7a 100644 --- a/src/detection/localip/localip_windows.c +++ b/src/detection/localip/localip_windows.c @@ -2,27 +2,269 @@ #include #include "common/netif.h" -#include "common/mallocHelper.h" #include "common/windows/unicode.h" #include "common/debug.h" #include "localip.h" -#define FF_LOCALIP_NIFLAG(name) { IP_ADAPTER_##name, #name } +static inline void appendFlag(FFstrbuf* flags, bool enabled, const char* name) { + if (!enabled) { + return; + } -static const FFLocalIpNIFlag niFlagOptions[] = { - FF_LOCALIP_NIFLAG(DDNS_ENABLED), - FF_LOCALIP_NIFLAG(REGISTER_ADAPTER_SUFFIX), - FF_LOCALIP_NIFLAG(DHCP_ENABLED), - FF_LOCALIP_NIFLAG(RECEIVE_ONLY), - FF_LOCALIP_NIFLAG(NO_MULTICAST), - FF_LOCALIP_NIFLAG(IPV6_OTHER_STATEFUL_CONFIG), - FF_LOCALIP_NIFLAG(NETBIOS_OVER_TCPIP_ENABLED), - FF_LOCALIP_NIFLAG(IPV4_ENABLED), - FF_LOCALIP_NIFLAG(IPV6_ENABLED), - FF_LOCALIP_NIFLAG(IPV6_MANAGE_ADDRESS_CONFIG), - // sentinel - {}, -}; + if (flags->length) { + ffStrbufAppendC(flags, ','); + } + ffStrbufAppendS(flags, name); +} + +static void fillInterfaceFlags(FFstrbuf* flags, const MIB_IF_ROW2* row) { + appendFlag(flags, row->InterfaceAndOperStatusFlags.HardwareInterface, "HARDWARE"); + appendFlag(flags, row->InterfaceAndOperStatusFlags.FilterInterface, "FILTER"); + appendFlag(flags, row->InterfaceAndOperStatusFlags.ConnectorPresent, "CONNECTOR_PRESENT"); + appendFlag(flags, row->InterfaceAndOperStatusFlags.NotAuthenticated, "NOT_AUTHENTICATED"); + appendFlag(flags, row->InterfaceAndOperStatusFlags.NotMediaConnected, "NO_MEDIA"); + appendFlag(flags, row->InterfaceAndOperStatusFlags.Paused, "PAUSED"); + appendFlag(flags, row->InterfaceAndOperStatusFlags.LowPower, "LOW_POWER"); + appendFlag(flags, row->InterfaceAndOperStatusFlags.EndPointInterface, "ENDPOINT"); + + switch (row->DirectionType) { + case NET_IF_DIRECTION_SENDONLY: + appendFlag(flags, true, "SEND_ONLY"); + break; + case NET_IF_DIRECTION_RECEIVEONLY: + appendFlag(flags, true, "RECEIVE_ONLY"); + break; + case NET_IF_DIRECTION_SENDRECEIVE: + appendFlag(flags, true, "SEND_RECEIVE"); + break; + default: + break; + } + + // Exactly one value of each of these enums is reported per interface, so every value gets + // a flag of its own +#define FF_LOCALIP_FLAG_CASE(value, name) \ + case value: appendFlag(flags, true, name); break; + + switch (row->MediaConnectState) { + FF_LOCALIP_FLAG_CASE(MediaConnectStateUnknown, "MEDIA_UNKNOWN") + FF_LOCALIP_FLAG_CASE(MediaConnectStateConnected, "MEDIA_CONNECTED") + FF_LOCALIP_FLAG_CASE(MediaConnectStateDisconnected, "MEDIA_DISCONNECTED") + } + + // The interface type, as registered with IANA and listed in ipifcons.h + switch (row->Type) { + FF_LOCALIP_FLAG_CASE(IF_TYPE_OTHER, "OTHER") + FF_LOCALIP_FLAG_CASE(IF_TYPE_REGULAR_1822, "REGULAR_1822") + FF_LOCALIP_FLAG_CASE(IF_TYPE_HDH_1822, "HDH_1822") + FF_LOCALIP_FLAG_CASE(IF_TYPE_DDN_X25, "DDN_X25") + FF_LOCALIP_FLAG_CASE(IF_TYPE_RFC877_X25, "RFC877_X25") + FF_LOCALIP_FLAG_CASE(IF_TYPE_ETHERNET_CSMACD, "ETHERNET_CSMACD") + FF_LOCALIP_FLAG_CASE(IF_TYPE_IS088023_CSMACD, "IS088023_CSMACD") + FF_LOCALIP_FLAG_CASE(IF_TYPE_ISO88024_TOKENBUS, "ISO88024_TOKENBUS") + FF_LOCALIP_FLAG_CASE(IF_TYPE_ISO88025_TOKENRING, "ISO88025_TOKENRING") + FF_LOCALIP_FLAG_CASE(IF_TYPE_ISO88026_MAN, "ISO88026_MAN") + FF_LOCALIP_FLAG_CASE(IF_TYPE_STARLAN, "STARLAN") + FF_LOCALIP_FLAG_CASE(IF_TYPE_PROTEON_10MBIT, "PROTEON_10MBIT") + FF_LOCALIP_FLAG_CASE(IF_TYPE_PROTEON_80MBIT, "PROTEON_80MBIT") + FF_LOCALIP_FLAG_CASE(IF_TYPE_HYPERCHANNEL, "HYPERCHANNEL") + FF_LOCALIP_FLAG_CASE(IF_TYPE_FDDI, "FDDI") + FF_LOCALIP_FLAG_CASE(IF_TYPE_LAP_B, "LAP_B") + FF_LOCALIP_FLAG_CASE(IF_TYPE_SDLC, "SDLC") + FF_LOCALIP_FLAG_CASE(IF_TYPE_DS1, "DS1") + FF_LOCALIP_FLAG_CASE(IF_TYPE_E1, "E1") + FF_LOCALIP_FLAG_CASE(IF_TYPE_BASIC_ISDN, "BASIC_ISDN") + FF_LOCALIP_FLAG_CASE(IF_TYPE_PRIMARY_ISDN, "PRIMARY_ISDN") + FF_LOCALIP_FLAG_CASE(IF_TYPE_PROP_POINT2POINT_SERIAL, "PROP_POINT2POINT_SERIAL") + FF_LOCALIP_FLAG_CASE(IF_TYPE_PPP, "PPP") + FF_LOCALIP_FLAG_CASE(IF_TYPE_SOFTWARE_LOOPBACK, "SOFTWARE_LOOPBACK") + FF_LOCALIP_FLAG_CASE(IF_TYPE_EON, "EON") + FF_LOCALIP_FLAG_CASE(IF_TYPE_ETHERNET_3MBIT, "ETHERNET_3MBIT") + FF_LOCALIP_FLAG_CASE(IF_TYPE_NSIP, "NSIP") + FF_LOCALIP_FLAG_CASE(IF_TYPE_SLIP, "SLIP") + FF_LOCALIP_FLAG_CASE(IF_TYPE_ULTRA, "ULTRA") + FF_LOCALIP_FLAG_CASE(IF_TYPE_DS3, "DS3") + FF_LOCALIP_FLAG_CASE(IF_TYPE_SIP, "SIP") + FF_LOCALIP_FLAG_CASE(IF_TYPE_FRAMERELAY, "FRAMERELAY") + FF_LOCALIP_FLAG_CASE(IF_TYPE_RS232, "RS232") + FF_LOCALIP_FLAG_CASE(IF_TYPE_PARA, "PARA") + FF_LOCALIP_FLAG_CASE(IF_TYPE_ARCNET, "ARCNET") + FF_LOCALIP_FLAG_CASE(IF_TYPE_ARCNET_PLUS, "ARCNET_PLUS") + FF_LOCALIP_FLAG_CASE(IF_TYPE_ATM, "ATM") + FF_LOCALIP_FLAG_CASE(IF_TYPE_MIO_X25, "MIO_X25") + FF_LOCALIP_FLAG_CASE(IF_TYPE_SONET, "SONET") + FF_LOCALIP_FLAG_CASE(IF_TYPE_X25_PLE, "X25_PLE") + FF_LOCALIP_FLAG_CASE(IF_TYPE_ISO88022_LLC, "ISO88022_LLC") + FF_LOCALIP_FLAG_CASE(IF_TYPE_LOCALTALK, "LOCALTALK") + FF_LOCALIP_FLAG_CASE(IF_TYPE_SMDS_DXI, "SMDS_DXI") + FF_LOCALIP_FLAG_CASE(IF_TYPE_FRAMERELAY_SERVICE, "FRAMERELAY_SERVICE") + FF_LOCALIP_FLAG_CASE(IF_TYPE_V35, "V35") + FF_LOCALIP_FLAG_CASE(IF_TYPE_HSSI, "HSSI") + FF_LOCALIP_FLAG_CASE(IF_TYPE_HIPPI, "HIPPI") + FF_LOCALIP_FLAG_CASE(IF_TYPE_MODEM, "MODEM") + FF_LOCALIP_FLAG_CASE(IF_TYPE_AAL5, "AAL5") + FF_LOCALIP_FLAG_CASE(IF_TYPE_SONET_PATH, "SONET_PATH") + FF_LOCALIP_FLAG_CASE(IF_TYPE_SONET_VT, "SONET_VT") + FF_LOCALIP_FLAG_CASE(IF_TYPE_SMDS_ICIP, "SMDS_ICIP") + FF_LOCALIP_FLAG_CASE(IF_TYPE_PROP_VIRTUAL, "PROP_VIRTUAL") + FF_LOCALIP_FLAG_CASE(IF_TYPE_PROP_MULTIPLEXOR, "PROP_MULTIPLEXOR") + FF_LOCALIP_FLAG_CASE(IF_TYPE_IEEE80212, "IEEE80212") + FF_LOCALIP_FLAG_CASE(IF_TYPE_FIBRECHANNEL, "FIBRECHANNEL") + FF_LOCALIP_FLAG_CASE(IF_TYPE_HIPPIINTERFACE, "HIPPIINTERFACE") + FF_LOCALIP_FLAG_CASE(IF_TYPE_FRAMERELAY_INTERCONNECT, "FRAMERELAY_INTERCONNECT") + FF_LOCALIP_FLAG_CASE(IF_TYPE_AFLANE_8023, "AFLANE_8023") + FF_LOCALIP_FLAG_CASE(IF_TYPE_AFLANE_8025, "AFLANE_8025") + FF_LOCALIP_FLAG_CASE(IF_TYPE_CCTEMUL, "CCTEMUL") + FF_LOCALIP_FLAG_CASE(IF_TYPE_FASTETHER, "FASTETHER") + FF_LOCALIP_FLAG_CASE(IF_TYPE_ISDN, "ISDN") + FF_LOCALIP_FLAG_CASE(IF_TYPE_V11, "V11") + FF_LOCALIP_FLAG_CASE(IF_TYPE_V36, "V36") + FF_LOCALIP_FLAG_CASE(IF_TYPE_G703_64K, "G703_64K") + FF_LOCALIP_FLAG_CASE(IF_TYPE_G703_2MB, "G703_2MB") + FF_LOCALIP_FLAG_CASE(IF_TYPE_QLLC, "QLLC") + FF_LOCALIP_FLAG_CASE(IF_TYPE_FASTETHER_FX, "FASTETHER_FX") + FF_LOCALIP_FLAG_CASE(IF_TYPE_CHANNEL, "CHANNEL") + FF_LOCALIP_FLAG_CASE(IF_TYPE_IEEE80211, "IEEE80211") + FF_LOCALIP_FLAG_CASE(IF_TYPE_IBM370PARCHAN, "IBM370PARCHAN") + FF_LOCALIP_FLAG_CASE(IF_TYPE_ESCON, "ESCON") + FF_LOCALIP_FLAG_CASE(IF_TYPE_DLSW, "DLSW") + FF_LOCALIP_FLAG_CASE(IF_TYPE_ISDN_S, "ISDN_S") + FF_LOCALIP_FLAG_CASE(IF_TYPE_ISDN_U, "ISDN_U") + FF_LOCALIP_FLAG_CASE(IF_TYPE_LAP_D, "LAP_D") + FF_LOCALIP_FLAG_CASE(IF_TYPE_IPSWITCH, "IPSWITCH") + FF_LOCALIP_FLAG_CASE(IF_TYPE_RSRB, "RSRB") + FF_LOCALIP_FLAG_CASE(IF_TYPE_ATM_LOGICAL, "ATM_LOGICAL") + FF_LOCALIP_FLAG_CASE(IF_TYPE_DS0, "DS0") + FF_LOCALIP_FLAG_CASE(IF_TYPE_DS0_BUNDLE, "DS0_BUNDLE") + FF_LOCALIP_FLAG_CASE(IF_TYPE_BSC, "BSC") + FF_LOCALIP_FLAG_CASE(IF_TYPE_ASYNC, "ASYNC") + FF_LOCALIP_FLAG_CASE(IF_TYPE_CNR, "CNR") + FF_LOCALIP_FLAG_CASE(IF_TYPE_ISO88025R_DTR, "ISO88025R_DTR") + FF_LOCALIP_FLAG_CASE(IF_TYPE_EPLRS, "EPLRS") + FF_LOCALIP_FLAG_CASE(IF_TYPE_ARAP, "ARAP") + FF_LOCALIP_FLAG_CASE(IF_TYPE_PROP_CNLS, "PROP_CNLS") + FF_LOCALIP_FLAG_CASE(IF_TYPE_HOSTPAD, "HOSTPAD") + FF_LOCALIP_FLAG_CASE(IF_TYPE_TERMPAD, "TERMPAD") + FF_LOCALIP_FLAG_CASE(IF_TYPE_FRAMERELAY_MPI, "FRAMERELAY_MPI") + FF_LOCALIP_FLAG_CASE(IF_TYPE_X213, "X213") + FF_LOCALIP_FLAG_CASE(IF_TYPE_ADSL, "ADSL") + FF_LOCALIP_FLAG_CASE(IF_TYPE_RADSL, "RADSL") + FF_LOCALIP_FLAG_CASE(IF_TYPE_SDSL, "SDSL") + FF_LOCALIP_FLAG_CASE(IF_TYPE_VDSL, "VDSL") + FF_LOCALIP_FLAG_CASE(IF_TYPE_ISO88025_CRFPRINT, "ISO88025_CRFPRINT") + FF_LOCALIP_FLAG_CASE(IF_TYPE_MYRINET, "MYRINET") + FF_LOCALIP_FLAG_CASE(IF_TYPE_VOICE_EM, "VOICE_EM") + FF_LOCALIP_FLAG_CASE(IF_TYPE_VOICE_FXO, "VOICE_FXO") + FF_LOCALIP_FLAG_CASE(IF_TYPE_VOICE_FXS, "VOICE_FXS") + FF_LOCALIP_FLAG_CASE(IF_TYPE_VOICE_ENCAP, "VOICE_ENCAP") + FF_LOCALIP_FLAG_CASE(IF_TYPE_VOICE_OVERIP, "VOICE_OVERIP") + FF_LOCALIP_FLAG_CASE(IF_TYPE_ATM_DXI, "ATM_DXI") + FF_LOCALIP_FLAG_CASE(IF_TYPE_ATM_FUNI, "ATM_FUNI") + FF_LOCALIP_FLAG_CASE(IF_TYPE_ATM_IMA, "ATM_IMA") + FF_LOCALIP_FLAG_CASE(IF_TYPE_PPPMULTILINKBUNDLE, "PPPMULTILINKBUNDLE") + FF_LOCALIP_FLAG_CASE(IF_TYPE_IPOVER_CDLC, "IPOVER_CDLC") + FF_LOCALIP_FLAG_CASE(IF_TYPE_IPOVER_CLAW, "IPOVER_CLAW") + FF_LOCALIP_FLAG_CASE(IF_TYPE_STACKTOSTACK, "STACKTOSTACK") + FF_LOCALIP_FLAG_CASE(IF_TYPE_VIRTUALIPADDRESS, "VIRTUALIPADDRESS") + FF_LOCALIP_FLAG_CASE(IF_TYPE_MPC, "MPC") + FF_LOCALIP_FLAG_CASE(IF_TYPE_IPOVER_ATM, "IPOVER_ATM") + FF_LOCALIP_FLAG_CASE(IF_TYPE_ISO88025_FIBER, "ISO88025_FIBER") + FF_LOCALIP_FLAG_CASE(IF_TYPE_TDLC, "TDLC") + FF_LOCALIP_FLAG_CASE(IF_TYPE_GIGABITETHERNET, "GIGABITETHERNET") + FF_LOCALIP_FLAG_CASE(IF_TYPE_HDLC, "HDLC") + FF_LOCALIP_FLAG_CASE(IF_TYPE_LAP_F, "LAP_F") + FF_LOCALIP_FLAG_CASE(IF_TYPE_V37, "V37") + FF_LOCALIP_FLAG_CASE(IF_TYPE_X25_MLP, "X25_MLP") + FF_LOCALIP_FLAG_CASE(IF_TYPE_X25_HUNTGROUP, "X25_HUNTGROUP") + FF_LOCALIP_FLAG_CASE(IF_TYPE_TRANSPHDLC, "TRANSPHDLC") + FF_LOCALIP_FLAG_CASE(IF_TYPE_INTERLEAVE, "INTERLEAVE") + FF_LOCALIP_FLAG_CASE(IF_TYPE_FAST, "FAST") + FF_LOCALIP_FLAG_CASE(IF_TYPE_IP, "IP") + FF_LOCALIP_FLAG_CASE(IF_TYPE_DOCSCABLE_MACLAYER, "DOCSCABLE_MACLAYER") + FF_LOCALIP_FLAG_CASE(IF_TYPE_DOCSCABLE_DOWNSTREAM, "DOCSCABLE_DOWNSTREAM") + FF_LOCALIP_FLAG_CASE(IF_TYPE_DOCSCABLE_UPSTREAM, "DOCSCABLE_UPSTREAM") + FF_LOCALIP_FLAG_CASE(IF_TYPE_A12MPPSWITCH, "A12MPPSWITCH") + FF_LOCALIP_FLAG_CASE(IF_TYPE_TUNNEL, "TUNNEL") + FF_LOCALIP_FLAG_CASE(IF_TYPE_COFFEE, "COFFEE") + FF_LOCALIP_FLAG_CASE(IF_TYPE_CES, "CES") + FF_LOCALIP_FLAG_CASE(IF_TYPE_ATM_SUBINTERFACE, "ATM_SUBINTERFACE") + FF_LOCALIP_FLAG_CASE(IF_TYPE_L2_VLAN, "L2_VLAN") + FF_LOCALIP_FLAG_CASE(IF_TYPE_L3_IPVLAN, "L3_IPVLAN") + FF_LOCALIP_FLAG_CASE(IF_TYPE_L3_IPXVLAN, "L3_IPXVLAN") + FF_LOCALIP_FLAG_CASE(IF_TYPE_DIGITALPOWERLINE, "DIGITALPOWERLINE") + FF_LOCALIP_FLAG_CASE(IF_TYPE_MEDIAMAILOVERIP, "MEDIAMAILOVERIP") + FF_LOCALIP_FLAG_CASE(IF_TYPE_DTM, "DTM") + FF_LOCALIP_FLAG_CASE(IF_TYPE_DCN, "DCN") + FF_LOCALIP_FLAG_CASE(IF_TYPE_IPFORWARD, "IPFORWARD") + FF_LOCALIP_FLAG_CASE(IF_TYPE_MSDSL, "MSDSL") + FF_LOCALIP_FLAG_CASE(IF_TYPE_IEEE1394, "IEEE1394") + FF_LOCALIP_FLAG_CASE(IF_TYPE_IF_GSN, "IF_GSN") + FF_LOCALIP_FLAG_CASE(IF_TYPE_DVBRCC_MACLAYER, "DVBRCC_MACLAYER") + FF_LOCALIP_FLAG_CASE(IF_TYPE_DVBRCC_DOWNSTREAM, "DVBRCC_DOWNSTREAM") + FF_LOCALIP_FLAG_CASE(IF_TYPE_DVBRCC_UPSTREAM, "DVBRCC_UPSTREAM") + FF_LOCALIP_FLAG_CASE(IF_TYPE_ATM_VIRTUAL, "ATM_VIRTUAL") + FF_LOCALIP_FLAG_CASE(IF_TYPE_MPLS_TUNNEL, "MPLS_TUNNEL") + FF_LOCALIP_FLAG_CASE(IF_TYPE_SRP, "SRP") + FF_LOCALIP_FLAG_CASE(IF_TYPE_VOICEOVERATM, "VOICEOVERATM") + FF_LOCALIP_FLAG_CASE(IF_TYPE_VOICEOVERFRAMERELAY, "VOICEOVERFRAMERELAY") + FF_LOCALIP_FLAG_CASE(IF_TYPE_IDSL, "IDSL") + FF_LOCALIP_FLAG_CASE(IF_TYPE_COMPOSITELINK, "COMPOSITELINK") + FF_LOCALIP_FLAG_CASE(IF_TYPE_SS7_SIGLINK, "SS7_SIGLINK") + FF_LOCALIP_FLAG_CASE(IF_TYPE_PROP_WIRELESS_P2P, "PROP_WIRELESS_P2P") + FF_LOCALIP_FLAG_CASE(IF_TYPE_FR_FORWARD, "FR_FORWARD") + FF_LOCALIP_FLAG_CASE(IF_TYPE_RFC1483, "RFC1483") + FF_LOCALIP_FLAG_CASE(IF_TYPE_USB, "USB") + FF_LOCALIP_FLAG_CASE(IF_TYPE_IEEE8023AD_LAG, "IEEE8023AD_LAG") + FF_LOCALIP_FLAG_CASE(IF_TYPE_BGP_POLICY_ACCOUNTING, "BGP_POLICY_ACCOUNTING") + FF_LOCALIP_FLAG_CASE(IF_TYPE_FRF16_MFR_BUNDLE, "FRF16_MFR_BUNDLE") + FF_LOCALIP_FLAG_CASE(IF_TYPE_H323_GATEKEEPER, "H323_GATEKEEPER") + FF_LOCALIP_FLAG_CASE(IF_TYPE_H323_PROXY, "H323_PROXY") + FF_LOCALIP_FLAG_CASE(IF_TYPE_MPLS, "MPLS") + FF_LOCALIP_FLAG_CASE(IF_TYPE_MF_SIGLINK, "MF_SIGLINK") + FF_LOCALIP_FLAG_CASE(IF_TYPE_HDSL2, "HDSL2") + FF_LOCALIP_FLAG_CASE(IF_TYPE_SHDSL, "SHDSL") + FF_LOCALIP_FLAG_CASE(IF_TYPE_DS1_FDL, "DS1_FDL") + FF_LOCALIP_FLAG_CASE(IF_TYPE_POS, "POS") + FF_LOCALIP_FLAG_CASE(IF_TYPE_DVB_ASI_IN, "DVB_ASI_IN") + FF_LOCALIP_FLAG_CASE(IF_TYPE_DVB_ASI_OUT, "DVB_ASI_OUT") + FF_LOCALIP_FLAG_CASE(IF_TYPE_PLC, "PLC") + FF_LOCALIP_FLAG_CASE(IF_TYPE_NFAS, "NFAS") + FF_LOCALIP_FLAG_CASE(IF_TYPE_TR008, "TR008") + FF_LOCALIP_FLAG_CASE(IF_TYPE_GR303_RDT, "GR303_RDT") + FF_LOCALIP_FLAG_CASE(IF_TYPE_GR303_IDT, "GR303_IDT") + FF_LOCALIP_FLAG_CASE(IF_TYPE_ISUP, "ISUP") + FF_LOCALIP_FLAG_CASE(IF_TYPE_PROP_DOCS_WIRELESS_MACLAYER, "PROP_DOCS_WIRELESS_MACLAYER") + FF_LOCALIP_FLAG_CASE(IF_TYPE_PROP_DOCS_WIRELESS_DOWNSTREAM, "PROP_DOCS_WIRELESS_DOWNSTREAM") + FF_LOCALIP_FLAG_CASE(IF_TYPE_PROP_DOCS_WIRELESS_UPSTREAM, "PROP_DOCS_WIRELESS_UPSTREAM") + FF_LOCALIP_FLAG_CASE(IF_TYPE_HIPERLAN2, "HIPERLAN2") + FF_LOCALIP_FLAG_CASE(IF_TYPE_PROP_BWA_P2MP, "PROP_BWA_P2MP") + FF_LOCALIP_FLAG_CASE(IF_TYPE_SONET_OVERHEAD_CHANNEL, "SONET_OVERHEAD_CHANNEL") + FF_LOCALIP_FLAG_CASE(IF_TYPE_DIGITAL_WRAPPER_OVERHEAD_CHANNEL, "DIGITAL_WRAPPER_OVERHEAD_CHANNEL") + FF_LOCALIP_FLAG_CASE(IF_TYPE_AAL2, "AAL2") + FF_LOCALIP_FLAG_CASE(IF_TYPE_RADIO_MAC, "RADIO_MAC") + FF_LOCALIP_FLAG_CASE(IF_TYPE_ATM_RADIO, "ATM_RADIO") + FF_LOCALIP_FLAG_CASE(IF_TYPE_IMT, "IMT") + FF_LOCALIP_FLAG_CASE(IF_TYPE_MVL, "MVL") + FF_LOCALIP_FLAG_CASE(IF_TYPE_REACH_DSL, "REACH_DSL") + FF_LOCALIP_FLAG_CASE(IF_TYPE_FR_DLCI_ENDPT, "FR_DLCI_ENDPT") + FF_LOCALIP_FLAG_CASE(IF_TYPE_ATM_VCI_ENDPT, "ATM_VCI_ENDPT") + FF_LOCALIP_FLAG_CASE(IF_TYPE_OPTICAL_CHANNEL, "OPTICAL_CHANNEL") + FF_LOCALIP_FLAG_CASE(IF_TYPE_OPTICAL_TRANSPORT, "OPTICAL_TRANSPORT") + FF_LOCALIP_FLAG_CASE(IF_TYPE_IEEE80216_WMAN, "IEEE80216_WMAN") + FF_LOCALIP_FLAG_CASE(IF_TYPE_WWANPP, "WWANPP") + FF_LOCALIP_FLAG_CASE(IF_TYPE_WWANPP2, "WWANPP2") + FF_LOCALIP_FLAG_CASE(IF_TYPE_IEEE802154, "IEEE802154") + FF_LOCALIP_FLAG_CASE(IF_TYPE_XBOX_WIRELESS, "XBOX_WIRELESS") + } + +#undef FF_LOCALIP_FLAG_CASE +} + +static bool isDefaultRouteInterface(const FFLocalIpOptions* options, NET_IFINDEX ifIndex) { + // Both route lookups are cached, so this is cheap even inside the per interface loop + return ((options->showType & FF_LOCALIP_TYPE_IPV4_BIT) && ffNetifGetDefaultRouteV4()->ifIndex == ifIndex) || + ((options->showType & FF_LOCALIP_TYPE_IPV6_BIT) && ffNetifGetDefaultRouteV6()->ifIndex == ifIndex); +} const char* ffDetectLocalIps(const FFLocalIpOptions* options, FFlist* results) { FF_DEBUG("Starting local IP detection with showType=0x%X, namePrefix='%.*s'", @@ -30,89 +272,102 @@ const char* ffDetectLocalIps(const FFLocalIpOptions* options, FFlist* results) { (int) options->namePrefix.length, options->namePrefix.chars); - FF_AUTO_FREE IP_ADAPTER_ADDRESSES* adapter_addresses = nullptr; + ADDRESS_FAMILY family = options->showType & FF_LOCALIP_TYPE_IPV4_BIT + ? options->showType & FF_LOCALIP_TYPE_IPV6_BIT ? AF_UNSPEC : AF_INET + : AF_INET6; + bool queryV4 = (options->showType & FF_LOCALIP_TYPE_IPV4_BIT) != 0; + bool queryV6 = (options->showType & FF_LOCALIP_TYPE_IPV6_BIT) != 0; - // 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) { - FF_DEBUG("Attempt %d to get adapter addresses, buffer size: %lu", attempts + 1, adapter_addresses_buffer_size); - - if (adapter_addresses_buffer_size) { - adapter_addresses = (IP_ADAPTER_ADDRESSES*) realloc(adapter_addresses, adapter_addresses_buffer_size); - assert(adapter_addresses); + // Every interface is queried with GetIfEntry2() rather than by walking a table of all of them: + // GetIfTable2() costs about 0.95 ms on a machine with a few dozen adapters however few of them + // are reported, because it marshals every registered one, while GetIfEntry2() costs about 24 us + // per interface. With 52 adapters registered and 8 interfaces carrying an address the per + // interface API is 3.6 times faster overall; the table only wins past roughly 40 interfaces with + // an address, and with FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT (the default) at most one interface + // can pass the filter below anyway. + bool defaultRouteOnly = (options->showType & FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT) != 0; + if (defaultRouteOnly) { + if (queryV4) { + FF_DEBUG("Querying the IPv4 default route interface only: ifIndex=%u", (unsigned) ffNetifGetDefaultRouteV4()->ifIndex); } - - DWORD family = options->showType & FF_LOCALIP_TYPE_IPV4_BIT - ? options->showType & FF_LOCALIP_TYPE_IPV6_BIT ? AF_UNSPEC : AF_INET - : AF_INET6; - FF_DEBUG("Calling GetAdaptersAddresses with family=%u, flags=0x%X", (unsigned) family, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER); - - DWORD error = GetAdaptersAddresses( - family, - GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER, - nullptr, - adapter_addresses, - &adapter_addresses_buffer_size); - - if (error == ERROR_SUCCESS) { - FF_DEBUG("GetAdaptersAddresses succeeded on attempt %d", attempts + 1); - break; - } else if (ERROR_BUFFER_OVERFLOW == error && attempts < 4) { - FF_DEBUG("Buffer overflow, need %lu bytes, retrying", adapter_addresses_buffer_size); - continue; - } else { - FF_DEBUG("GetAdaptersAddresses failed with error %lu after %d attempts", error, attempts + 1); - return "GetAdaptersAddresses() failed"; + if (queryV6) { + FF_DEBUG("Querying the IPv6 default route interface only: ifIndex=%u", (unsigned) ffNetifGetDefaultRouteV6()->ifIndex); } } + PMIB_UNICASTIPADDRESS_TABLE table = nullptr; + const char* errorMessage = nullptr; + + // The unicast address table is the only API that reports per interface addresses and it cannot + // be filtered by interface, so it is queried once and only the rows of the interfaces which pass + // the filter below are read + if (!NETIO_SUCCESS(GetUnicastIpAddressTable(family, &table))) { + FF_DEBUG("GetUnicastIpAddressTable failed"); + errorMessage = "GetUnicastIpAddressTable() failed"; + goto cleanup; + } + [[maybe_unused]] int adapterCount = 0, processedCount = 0; - // Iterate through all of the adapters - for (IP_ADAPTER_ADDRESSES* adapter = adapter_addresses; adapter; adapter = adapter->Next) { + // Rows of the same interface are not adjacent (IPv4 rows come first), + // so each interface is handled at its first row + for (ULONG i = 0; i < table->NumEntries; ++i) { + NET_IFINDEX ifIndex = table->Table[i].InterfaceIndex; + + bool seen = false; + for (ULONG j = 0; j < i && !seen; ++j) { + seen = table->Table[j].InterfaceIndex == ifIndex; + } + if (seen) { + continue; + } + adapterCount++; + // Checked first to avoid querying interfaces we won't show + if (defaultRouteOnly && !isDefaultRouteInterface(options, ifIndex)) { + FF_DEBUG("Skipping interface %u (not default route interface)", (unsigned) ifIndex); + continue; + } + + MIB_IF_ROW2 ifRow = { .InterfaceIndex = ifIndex }; + if (!NETIO_SUCCESS(GetIfEntry2(&ifRow))) { + FF_DEBUG("GetIfEntry2(%u) failed", (unsigned) ifIndex); + continue; + } + FF_DEBUG("Processing adapter %d: IfIndex=%u, IfType=%u, OperStatus=%u", adapterCount, - (unsigned) adapter->IfIndex, - (unsigned) adapter->IfType, - (unsigned) adapter->OperStatus); + (unsigned) ifIndex, + (unsigned) ifRow.Type, + (unsigned) ifRow.OperStatus); - if (adapter->OperStatus != IfOperStatusUp) { - FF_DEBUG("Skipping adapter %u (not operational, status=%d)", (unsigned) adapter->IfIndex, adapter->OperStatus); + if (ifRow.OperStatus != IfOperStatusUp) { + FF_DEBUG("Skipping adapter %u (not operational, status=%d)", (unsigned) ifIndex, ifRow.OperStatus); continue; } - bool isLoop = adapter->IfType == IF_TYPE_SOFTWARE_LOOPBACK; - FF_DEBUG("Adapter %u: isLoopback=%s", (unsigned) adapter->IfIndex, isLoop ? "true" : "false"); + bool isLoop = ifRow.Type == IF_TYPE_SOFTWARE_LOOPBACK; + FF_DEBUG("Adapter %u: isLoopback=%s", (unsigned) ifIndex, isLoop ? "true" : "false"); if (isLoop && !(options->showType & FF_LOCALIP_TYPE_LOOP_BIT)) { - FF_DEBUG("Skipping loopback adapter %u (loopback not requested)", (unsigned) adapter->IfIndex); + FF_DEBUG("Skipping loopback adapter %u (loopback not requested)", (unsigned) ifIndex); continue; } - FF_STRBUF_AUTO_DESTROY name = ffStrbufCreateWS(adapter->FriendlyName); - FF_DEBUG("Adapter %u name: '%s'", (unsigned) adapter->IfIndex, name.chars); + FF_STRBUF_AUTO_DESTROY name = ffStrbufCreateWS(ifRow.Alias); + FF_DEBUG("Adapter %u name: '%s'", (unsigned) ifIndex, name.chars); if (options->namePrefix.length && !ffStrbufStartsWith(&name, &options->namePrefix)) { FF_DEBUG("Skipping adapter %u (name doesn't match prefix '%.*s')", - (unsigned) adapter->IfIndex, + (unsigned) ifIndex, (int) options->namePrefix.length, options->namePrefix.chars); continue; } - if (options->showType & FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT) { - if (!((options->showType & FF_LOCALIP_TYPE_IPV4_BIT) && ffNetifGetDefaultRouteV4()->ifIndex == adapter->IfIndex) && - !((options->showType & FF_LOCALIP_TYPE_IPV6_BIT) && ffNetifGetDefaultRouteV6()->ifIndex == adapter->IfIndex)) { - FF_DEBUG("Skipping interface %u (not default route interface)", (unsigned) adapter->IfIndex); - continue; - } - } - processedCount++; - FF_DEBUG("Creating result item for adapter %u ('%s')", (unsigned) adapter->IfIndex, name.chars); + FF_DEBUG("Creating result item for adapter %u ('%s')", (unsigned) ifIndex, name.chars); FFLocalIpResult* item = FF_LIST_ADD(FFLocalIpResult, *results); ffStrbufInitMove(&item->name, &name); @@ -124,16 +379,26 @@ const char* ffDetectLocalIps(const FFLocalIpOptions* options, FFlist* results) { item->speed = -1; item->mtu = -1; + if (options->showType & FF_LOCALIP_TYPE_FLAGS_BIT) { + fillInterfaceFlags(&item->flags, &ifRow); + FF_DEBUG("Adapter %u flags: '%s'", (unsigned) ifIndex, item->flags.chars); + } + uint32_t typesToAdd = options->showType & (FF_LOCALIP_TYPE_IPV4_BIT | FF_LOCALIP_TYPE_IPV6_BIT | FF_LOCALIP_TYPE_ALL_IPS_BIT); - FF_DEBUG("Types to add for adapter %u: 0x%X", (unsigned) adapter->IfIndex, typesToAdd); + FF_DEBUG("Types to add for adapter %u: 0x%X", (unsigned) ifIndex, typesToAdd); [[maybe_unused]] int ipv4Count = 0, ipv6Count = 0; - for (IP_ADAPTER_UNICAST_ADDRESS* ifa = adapter->FirstUnicastAddress; ifa; ifa = ifa->Next) { + for (ULONG j = i; j < table->NumEntries; ++j) { + MIB_UNICASTIPADDRESS_ROW* ifa = &table->Table[j]; + if (ifa->InterfaceIndex != ifIndex) { + continue; + } + FF_DEBUG("Processing unicast address: prefix origin=%d, suffix origin=%d, family=%d, DadState=%d", ifa->PrefixOrigin, ifa->SuffixOrigin, - ifa->Address.lpSockaddr->sa_family, + ifa->Address.si_family, ifa->DadState); if (!(options->showType & FF_LOCALIP_TYPE_ALL_IPS_BIT)) { @@ -150,19 +415,19 @@ const char* ffDetectLocalIps(const FFLocalIpOptions* options, FFlist* results) { // MIB_UNICASTIPADDRESS_ROW::SkipAsSource } - if (ifa->Address.lpSockaddr->sa_family == AF_INET) { + if (ifa->Address.si_family == AF_INET) { if (!(typesToAdd & (FF_LOCALIP_TYPE_IPV4_BIT | FF_LOCALIP_TYPE_ALL_IPS_BIT))) { FF_DEBUG("Skipping IPv4 address (not requested in typesToAdd=0x%X)", typesToAdd); continue; } - bool isDefaultRoute = ((options->showType & FF_LOCALIP_TYPE_IPV4_BIT) && ffNetifGetDefaultRouteV4()->ifIndex == adapter->IfIndex); - if ((options->showType & FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT) && !isDefaultRoute) { + bool isDefaultRoute = ((options->showType & FF_LOCALIP_TYPE_IPV4_BIT) && ffNetifGetDefaultRouteV4()->ifIndex == ifIndex); + if (defaultRouteOnly && !isDefaultRoute) { FF_DEBUG("Skipping IPv4 address (not on default route interface)"); continue; } - SOCKADDR_IN* ipv4 = (SOCKADDR_IN*) ifa->Address.lpSockaddr; + SOCKADDR_IN* ipv4 = &ifa->Address.Ipv4; char addressBuffer[INET_ADDRSTRLEN + 10]; char* end = RtlIpv4AddressToStringA(&ipv4->sin_addr, addressBuffer); @@ -185,13 +450,13 @@ const char* ffDetectLocalIps(const FFLocalIpOptions* options, FFlist* results) { if (typesToAdd == 0) { break; } - } else if (ifa->Address.lpSockaddr->sa_family == AF_INET6) { + } else if (ifa->Address.si_family == AF_INET6) { if (!(typesToAdd & (FF_LOCALIP_TYPE_IPV6_BIT | FF_LOCALIP_TYPE_ALL_IPS_BIT))) { FF_DEBUG("Skipping IPv6 address (not requested in typesToAdd=0x%X)", typesToAdd); continue; } - SOCKADDR_IN6* ipv6 = (SOCKADDR_IN6*) ifa->Address.lpSockaddr; + SOCKADDR_IN6* ipv6 = &ifa->Address.Ipv6; FFLocalIpIpv6Type ipv6Type = FF_LOCALIP_IPV6_TYPE_NONE; if (IN6_IS_ADDR_GLOBAL(&ipv6->sin6_addr)) { @@ -209,8 +474,8 @@ const char* ffDetectLocalIps(const FFLocalIpOptions* options, FFlist* results) { continue; } - bool isDefaultRoute = ((options->showType & FF_LOCALIP_TYPE_IPV6_BIT) && ffNetifGetDefaultRouteV6()->ifIndex == adapter->IfIndex); - if ((options->showType & FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT) && !isDefaultRoute) { + bool isDefaultRoute = ((options->showType & FF_LOCALIP_TYPE_IPV6_BIT) && ffNetifGetDefaultRouteV6()->ifIndex == ifIndex); + if (defaultRouteOnly && !isDefaultRoute) { FF_DEBUG("Skipping IPv6 address (not on default route interface)"); continue; } @@ -240,24 +505,20 @@ const char* ffDetectLocalIps(const FFLocalIpOptions* options, FFlist* results) { } } - FF_DEBUG("Adapter %u: collected %d IPv4 and %d IPv6 addresses", (unsigned) adapter->IfIndex, ipv4Count, ipv6Count); + FF_DEBUG("Adapter %u: collected %d IPv4 and %d IPv6 addresses", (unsigned) ifIndex, ipv4Count, ipv6Count); if (options->showType & FF_LOCALIP_TYPE_SPEED_BIT) { - item->speed = (int32_t) (adapter->ReceiveLinkSpeed / 1000000); - FF_DEBUG("Adapter %u speed: %d Mbps (raw: %llu)", (unsigned) adapter->IfIndex, item->speed, adapter->ReceiveLinkSpeed); + item->speed = (int32_t) (ifRow.ReceiveLinkSpeed / 1000000); + FF_DEBUG("Adapter %u speed: %d Mbps (raw: %llu)", (unsigned) ifIndex, item->speed, ifRow.ReceiveLinkSpeed); } if (options->showType & FF_LOCALIP_TYPE_MTU_BIT) { - item->mtu = (int32_t) adapter->Mtu; - FF_DEBUG("Adapter %u MTU: %d", (unsigned) adapter->IfIndex, item->mtu); + item->mtu = (int32_t) ifRow.Mtu; + FF_DEBUG("Adapter %u MTU: %d", (unsigned) ifIndex, item->mtu); } - if (options->showType & FF_LOCALIP_TYPE_FLAGS_BIT) { - ffLocalIpFillNIFlags(&item->flags, adapter->Flags, niFlagOptions); - FF_DEBUG("Adapter %u flags: 0x%lX -> '%s'", (unsigned) adapter->IfIndex, adapter->Flags, item->flags.chars); - } - if (options->showType & FF_LOCALIP_TYPE_MAC_BIT && adapter->PhysicalAddressLength == 6) { - uint8_t* ptr = adapter->PhysicalAddress; + if (options->showType & FF_LOCALIP_TYPE_MAC_BIT && ifRow.PhysicalAddressLength == 6) { + const uint8_t* ptr = ifRow.PhysicalAddress; ffStrbufSetF(&item->mac, "%02x:%02x:%02x:%02x:%02x:%02x", ptr[0], ptr[1], ptr[2], ptr[3], ptr[4], ptr[5]); - FF_DEBUG("Adapter %u MAC: %s", (unsigned) adapter->IfIndex, item->mac.chars); + FF_DEBUG("Adapter %u MAC: %s", (unsigned) ifIndex, item->mac.chars); } } @@ -266,5 +527,9 @@ const char* ffDetectLocalIps(const FFLocalIpOptions* options, FFlist* results) { processedCount, results->length); - return nullptr; +cleanup: + if (table) { + FreeMibTable(table); + } + return errorMessage; } diff --git a/src/detection/netio/netio_windows.c b/src/detection/netio/netio_windows.c index 4c3f87b3e..86ff930d6 100644 --- a/src/detection/netio/netio_windows.c +++ b/src/detection/netio/netio_windows.c @@ -1,71 +1,79 @@ #include "netio.h" #include "common/netif.h" -#include "common/mallocHelper.h" #include "common/windows/unicode.h" #include #include -const char* ffNetIOGetIoCounters(FFlist* result, FFNetIOOptions* options) { - FF_AUTO_FREE IP_ADAPTER_ADDRESSES* adapter_addresses = nullptr; - - // 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, - nullptr, - adapter_addresses, - &adapter_addresses_buffer_size); - - if (error == ERROR_SUCCESS) { - break; - } else if (ERROR_BUFFER_OVERFLOW == error && attempts < 4) { - continue; - } else { - return "GetAdaptersAddresses() failed"; - } +static void addInterface(FFlist* result, const FFNetIOOptions* options, NET_IFINDEX ifIndex, bool isDefaultRoute) { + MIB_IF_ROW2 ifRow = { .InterfaceIndex = ifIndex }; + if (!NETIO_SUCCESS(GetIfEntry2(&ifRow))) { + return; } + // localip reports the interfaces that are operational only, so the same filter is applied here + if (ifRow.OperStatus != IfOperStatusUp) { + return; + } + + FF_STRBUF_AUTO_DESTROY name = ffStrbufCreateWS(ifRow.Alias); + if (options->namePrefix.length && !ffStrbufStartsWith(&name, &options->namePrefix)) { + return; + } + + 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, + }; +} + +const char* ffNetIOGetIoCounters(FFlist* result, FFNetIOOptions* options) { 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, - }; - } + // With `defaultRouteOnly` (the default) the interface carrying the default route is the only one + // that can be reported, so it is queried with the single entry API instead of walking a table of + // every interface: GetIfEntry2() on one interface costs about 12 us. + if (options->defaultRouteOnly) { + addInterface(result, options, (NET_IFINDEX) defaultRouteIfIndex, true); + return nullptr; } + // The interface set is taken from the unicast address table, the same source localip uses, so + // both modules report the same interfaces. Walking the interface table instead would also report + // the NDIS filter layers of every adapter (Npcap, QoS, WFP, ...) and the adapters that carry no + // address at all, and those cannot be told apart from real adapters by any MIB_IF_ROW2 field. + // GetUnicastIpAddressTable() costs about 72 us, GetIfTable2() about 860 us. + PMIB_UNICASTIPADDRESS_TABLE addressTable = nullptr; + if (!NETIO_SUCCESS(GetUnicastIpAddressTable(AF_UNSPEC, &addressTable))) { + return "GetUnicastIpAddressTable() failed"; + } + + for (ULONG i = 0; i < addressTable->NumEntries; ++i) { + NET_IFINDEX ifIndex = addressTable->Table[i].InterfaceIndex; + + // Rows of the same interface are not adjacent (IPv4 rows come first), + // so each interface is handled at its first row + bool seen = false; + for (ULONG j = 0; j < i && !seen; ++j) { + seen = addressTable->Table[j].InterfaceIndex == ifIndex; + } + if (seen) { + continue; + } + + addInterface(result, options, ifIndex, ifIndex == defaultRouteIfIndex); + } + FreeMibTable(addressTable); + return nullptr; }