diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f9cbe6bb9..aea0124c4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# Unreleased + +Features: +* Improved LocalIP detection performance on Windows. (LocalIP, Windows) + # 2.69.0 Changes: diff --git a/src/detection/localip/localip_windows.c b/src/detection/localip/localip_windows.c index c36118015c..acc124772e 100644 --- a/src/detection/localip/localip_windows.c +++ b/src/detection/localip/localip_windows.c @@ -30,89 +30,115 @@ 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; - // 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); + // GetAdaptersAddresses() is slow (several ms, more with many virtual adapters), + // so it is only used for adapter flags, which are not available elsewhere + FF_AUTO_FREE IP_ADAPTER_ADDRESSES* adapter_addresses = nullptr; + if (options->showType & FF_LOCALIP_TYPE_FLAGS_BIT) { + // Multiple attempts in case interfaces change while + // we are in the middle of querying them. + DWORD adapter_addresses_buffer_size = 15 * 1024; // recommended by Microsoft + 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); - } - 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"; + DWORD error = GetAdaptersAddresses( + family, + GAA_FLAG_SKIP_UNICAST | GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_SKIP_FRIENDLY_NAME, + 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"; + } } } + PMIB_UNICASTIPADDRESS_TABLE table = nullptr; + if (!NETIO_SUCCESS(GetUnicastIpAddressTable(family, &table))) { + FF_DEBUG("GetUnicastIpAddressTable failed"); + return "GetUnicastIpAddressTable() failed"; + } + [[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 (options->showType & FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT) { + if (!((options->showType & FF_LOCALIP_TYPE_IPV4_BIT) && ffNetifGetDefaultRouteV4()->ifIndex == ifIndex) && + !((options->showType & FF_LOCALIP_TYPE_IPV6_BIT) && ffNetifGetDefaultRouteV6()->ifIndex == 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 failed for adapter %u", (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); @@ -125,15 +151,20 @@ const char* ffDetectLocalIps(const FFLocalIpOptions* options, FFlist* results) { item->mtu = -1; 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 +181,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); + bool isDefaultRoute = ((options->showType & FF_LOCALIP_TYPE_IPV4_BIT) && ffNetifGetDefaultRouteV4()->ifIndex == ifIndex); if ((options->showType & FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT) && !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 +216,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,7 +240,7 @@ const char* ffDetectLocalIps(const FFLocalIpOptions* options, FFlist* results) { continue; } - bool isDefaultRoute = ((options->showType & FF_LOCALIP_TYPE_IPV6_BIT) && ffNetifGetDefaultRouteV6()->ifIndex == adapter->IfIndex); + bool isDefaultRoute = ((options->showType & FF_LOCALIP_TYPE_IPV6_BIT) && ffNetifGetDefaultRouteV6()->ifIndex == ifIndex); if ((options->showType & FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT) && !isDefaultRoute) { FF_DEBUG("Skipping IPv6 address (not on default route interface)"); continue; @@ -240,27 +271,35 @@ 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); + // GetIfEntry2() reports 1500 for loopback, which has no real MTU + if (options->showType & FF_LOCALIP_TYPE_MTU_BIT && !isLoop) { + 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); + for (IP_ADAPTER_ADDRESSES* adapter = adapter_addresses; adapter; adapter = adapter->Next) { + if (adapter->IfIndex == ifIndex) { + ffLocalIpFillNIFlags(&item->flags, adapter->Flags, niFlagOptions); + FF_DEBUG("Adapter %u flags: 0x%lX -> '%s'", (unsigned) ifIndex, adapter->Flags, item->flags.chars); + break; + } + } } - 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) { + 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); } } + FreeMibTable(table); + FF_DEBUG("Local IP detection completed: scanned %d adapters, processed %d, results count: %u", adapterCount, processedCount,