Compare commits

...
Author SHA1 Message Date
Carter Li 70d2de4aee Update build-windows-hosts.yml 2026-10-02 23:18:25 +08:00
dependabot[bot] 790b21cda4 CI: Bump the github-actions group with 5 updates
Bumps the github-actions group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [benchmark-action/github-action-benchmark](https://github.com/benchmark-action/github-action-benchmark) | `1.22.1` | `1.22.2` |
| [github/codeql-action/init](https://github.com/github/codeql-action) | `4.38.0` | `4.38.1` |
| [github/codeql-action/analyze](https://github.com/github/codeql-action) | `4.38.0` | `4.38.1` |
| [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) | `4.3.0` | `4.4.0` |
| [signpath/github-action-submit-signing-request](https://github.com/signpath/github-action-submit-signing-request) | `2.3` | `3.0` |


Updates `benchmark-action/github-action-benchmark` from 1.22.1 to 1.22.2
- [Release notes](https://github.com/benchmark-action/github-action-benchmark/releases)
- [Changelog](https://github.com/benchmark-action/github-action-benchmark/blob/master/CHANGELOG.md)
- [Commits](https://github.com/benchmark-action/github-action-benchmark/compare/52576c92bccf6ac60c8223ec7eb2565637cae9ba...4322e5726e6334590d251fc4f92bec0efafc45dc)

Updates `github/codeql-action/init` from 4.38.0 to 4.38.1
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/b96794f015dfd88f77b49b1c93e0fa7110f94c63...1c5b675653bb5c22dbe9b12b556ec555138e09fd)

Updates `github/codeql-action/analyze` from 4.38.0 to 4.38.1
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/b96794f015dfd88f77b49b1c93e0fa7110f94c63...1c5b675653bb5c22dbe9b12b556ec555138e09fd)

Updates `docker/setup-qemu-action` from 4.3.0 to 4.4.0
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/1f40c72289eff860ee54a304f1438e3cff362e0a...99012661954931238ded8c8b007157a8430204e1)

Updates `signpath/github-action-submit-signing-request` from 2.3 to 3.0
- [Release notes](https://github.com/signpath/github-action-submit-signing-request/releases)
- [Commits](https://github.com/signpath/github-action-submit-signing-request/compare/c92b958760219087e01f8d67a1669ed57afe2627...f6d04783b4569d051e0c80105fe66e82819d0092)

---
updated-dependencies:
- dependency-name: benchmark-action/github-action-benchmark
  dependency-version: 1.22.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: github/codeql-action/init
  dependency-version: 4.38.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: github/codeql-action/analyze
  dependency-version: 4.38.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: docker/setup-qemu-action
  dependency-version: 4.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: signpath/github-action-submit-signing-request
  dependency-version: '3.0'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-27 18:07:11 +00:00
李通洲 80632920bb Doc: uses the temp domain of repology [ci skip] 2026-09-27 09:04:10 +08:00
李通洲 5246f149eb Doc: updates info about image logo on Windows 2026-09-27 09:03:44 +08:00
Joshua RogersandCarter Li f00a021e27 Completion (Bash): fixes --config/-c path completion splitting on spaces (#2621) [ci skip]
Co-authored-by: Carter Li <CarterLi@users.noreply.github.com>
2026-09-27 09:00:11 +08:00
İhsan Deniz 676dbcfee2 Vulkan: fixes driver name concatenation on multi-GPU systems (#2620)
* Vulkan: fixes driver name concatenation on multi-GPU systems

applyDriverName appended to the result buffer without clearing it. When
a later device reported a higher API version, its driver name was
appended to the name of the earlier device instead of replacing it, as
the comment in detectVulkan intends.

* CHANGELOG: moves the #2615 entry to Unreleased

#2616 was opened after 2.69.0 had been released, but its entry was
written under 2.69.0 Bugfixes, so the changelog listed the fix as part
of 2.69.0.
2026-09-27 08:35:20 +08:00
İhsan Deniz 9801d89316 Brightness (Linux): fixes random built-in flag and stdout leak in DDC/CI detection (#2616)
DDC/CI results never set `builtin`, and FF_LIST_ADD does not zero the new
element, so external monitors were reported as built-in or external at
random. Set it to false, as the BSD DDC/CI backend does.

FF_SUPPRESS_IO() was declared inside the `if (ffddca_init)` block, so its
cleanup restored stdout before display detection ran and libddcutil trace
lines could reach the output, ahead of `--format json` too. Declare it at
function scope so it covers the whole detection, including the
FF_DISABLE_DLOPEN build.

Closes #2615
2026-09-26 18:18:43 +08:00
Carter Li fb52d32452 Packages (Linux): uses getdirent64(2) directly 2026-09-26 14:18:00 +08:00
李通洲 36dc82e926 Bootmgr: cleans code 2026-09-26 14:17:24 +08:00
李通洲 7fdd1d8e93 LocalIP / NetIO / DNS (Windows): improves performance
Fixes #2614
2026-09-26 14:17:24 +08:00
16 changed files with 768 additions and 257 deletions

No files matched your search

+1 -1
View File
@@ -29,7 +29,7 @@ jobs:
run: cp .github/benchmark-index.html "$RUNNER_TEMP/benchmark-index.html" run: cp .github/benchmark-index.html "$RUNNER_TEMP/benchmark-index.html"
- name: store benchmark result - name: store benchmark result
uses: benchmark-action/github-action-benchmark@52576c92bccf6ac60c8223ec7eb2565637cae9ba # v1.22.1 uses: benchmark-action/github-action-benchmark@4322e5726e6334590d251fc4f92bec0efafc45dc # v1.22.2
with: with:
name: Fastfetch name: Fastfetch
tool: customSmallerIsBetter tool: customSmallerIsBetter
+2 -2
View File
@@ -52,7 +52,7 @@ jobs:
- name: Initialize CodeQL - name: Initialize CodeQL
if: inputs.arch == 'amd64' if: inputs.arch == 'amd64'
uses: github/codeql-action/init@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0 uses: github/codeql-action/init@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1
with: with:
languages: c languages: c
@@ -64,7 +64,7 @@ jobs:
- name: perform CodeQL analysis - name: perform CodeQL analysis
if: inputs.arch == 'amd64' if: inputs.arch == 'amd64'
uses: github/codeql-action/analyze@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0 uses: github/codeql-action/analyze@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1
- name: list features - name: list features
run: ./fastfetch --list-features run: ./fastfetch --list-features
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: set up QEMU for loong64 - name: set up QEMU for loong64
uses: docker/setup-qemu-action@1f40c72289eff860ee54a304f1438e3cff362e0a # v4.3.0 uses: docker/setup-qemu-action@99012661954931238ded8c8b007157a8430204e1 # v4.4.0
- name: build in loong64 container - name: build in loong64 container
run: | run: |
+13
View File
@@ -1,3 +1,16 @@
# 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
Bugfixes:
* Fixed `--config`/`-c` bash completion offering one candidate per word instead of one candidate per file for paths containing spaces. (Completion, Bash)
* Fixed external monitors detected via DDC/CI being randomly reported as built-in, and libddcutil trace messages occasionally leaking into the output and breaking `--format json`. (#2615, Brightness / Linux)
* Fixed the Vulkan driver name being concatenated with the driver name of a previously enumerated device on systems with more than one GPU. (#2619, Vulkan)
# 2.69.0 # 2.69.0
Changes: Changes:
+19 -37
View File
@@ -9,8 +9,8 @@
[![homebrew downloads](https://img.shields.io/homebrew/installs/dm/fastfetch?logo=homebrew)](https://formulae.brew.sh/formula/fastfetch#default) [![homebrew downloads](https://img.shields.io/homebrew/installs/dm/fastfetch?logo=homebrew)](https://formulae.brew.sh/formula/fastfetch#default)
[![GitHub all releases](https://img.shields.io/github/downloads/fastfetch-cli/fastfetch/total?logo=github)](https://github.com/fastfetch-cli/fastfetch/releases) [![GitHub all releases](https://img.shields.io/github/downloads/fastfetch-cli/fastfetch/total?logo=github)](https://github.com/fastfetch-cli/fastfetch/releases)
[![GitHub release (with filter)](https://img.shields.io/github/v/release/fastfetch-cli/fastfetch?logo=github)](https://github.com/fastfetch-cli/fastfetch/releases) [![GitHub release (with filter)](https://img.shields.io/github/v/release/fastfetch-cli/fastfetch?logo=github)](https://github.com/fastfetch-cli/fastfetch/releases)
[![latest packaged version(s)](https://repology.org/badge/latest-versions/fastfetch.svg)](https://repology.org/project/fastfetch/versions) [![latest packaged version(s)](https://repology.amdmi3.ru/badge/latest-versions/fastfetch.svg)](https://repology.amdmi3.ru/project/fastfetch/versions)
[![Packaging status](https://repology.org/badge/tiny-repos/fastfetch.svg)](https://repology.org/project/fastfetch/versions) [![Packaging status](https://repology.amdmi3.ru/badge/tiny-repos/fastfetch.svg)](https://repology.amdmi3.ru/project/fastfetch/versions)
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/fastfetch-cli/fastfetch) [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/fastfetch-cli/fastfetch)
[![中文README](https://img.shields.io/badge/%E4%B8%AD%E6%96%87-README-red)](README-cn.md) [![中文README](https://img.shields.io/badge/%E4%B8%AD%E6%96%87-README-red)](README-cn.md)
@@ -36,8 +36,8 @@ There are [screenshots on different platforms](https://github.com/fastfetch-cli/
Some distributions package outdated versions of fastfetch. Older versions receive no support, so please always try to use the latest version. Some distributions package outdated versions of fastfetch. Older versions receive no support, so please always try to use the latest version.
<a href="https://repology.org/project/fastfetch/versions"> <a href="https://repology.amdmi3.ru/project/fastfetch/versions">
<img src="https://repology.org/badge/vertical-allrepos/fastfetch.svg?columns=2" alt="Packaging status" align="right"> <img src="https://repology.amdmi3.ru/badge/vertical-allrepos/fastfetch.svg?columns=2" alt="Packaging status" align="right">
</a> </a>
* Ubuntu: [`ppa:zhangsongcui3371/fastfetch`](https://launchpad.net/~zhangsongcui3371/+archive/ubuntu/fastfetch) (Ubuntu 22.04 or newer; latest version) * Ubuntu: [`ppa:zhangsongcui3371/fastfetch`](https://launchpad.net/~zhangsongcui3371/+archive/ubuntu/fastfetch) (Ubuntu 22.04 or newer; latest version)
@@ -248,7 +248,20 @@ Set the key to a white space.
### Q: How can I display images on Windows? ### Q: How can I display images on Windows?
As of April 2025: #### Windows Terminal
Windows Terminal supports the sixel image protocol only.
```jsonc
{
"logo": {
"type": "sixel", // DO NOT USE "auto"
"source": "C:/path/to/image.png", // ~ can be used to refer to the Windows user home directory
"width": <image-width-in-chars>, // Optional
"height": <image-height-in-chars> // Optional
}
}
```
#### mintty and Wezterm #### mintty and Wezterm
@@ -260,38 +273,7 @@ In `config.jsonc`:
"logo": { "logo": {
"type": "iterm", "type": "iterm",
"source": "C:/path/to/image.png", "source": "C:/path/to/image.png",
"width": <num-in-chars> "width": <image-width-in-chars>
}
}
```
#### Windows Terminal
Windows Terminal supports the sixel image protocol only.
* If you installed fastfetch through MSYS2:
1. Install imagemagick: `pacman -S mingw-w64-<subsystem>-x86_64-imagemagick`
2. In `config.jsonc`:
```jsonc
{
"logo": {
"type": "sixel", // DO NOT USE "auto"
"source": "C:/path/to/image.png", // Do NOT use `~` as fastfetch is a native Windows program and doesn't apply cygwin path conversion
"width": <image-width-in-chars>, // Optional
"height": <image-height-in-chars> // Optional
}
}
```
* If you installed fastfetch via scoop or downloaded the binary directly from the GitHub Releases page:
1. Convert your image manually to sixel format using [any online image conversion service](https://www.google.com/search?q=convert+image+to+sixel)
2. In `config.jsonc`:
```jsonc
{
"logo": {
"type": "raw", // DO NOT USE "auto"
"source": "C:/path/to/image.sixel",
"width": <image-width-in-chars>, // Required
"height": <image-height-in-chars> // Required
} }
} }
``` ```
+3 -1
View File
@@ -33,7 +33,9 @@ _fastfetch() {
if type _filedir &>/dev/null; then if type _filedir &>/dev/null; then
_filedir _filedir
elif type compgen &>/dev/null; then elif type compgen &>/dev/null; then
COMPREPLY+=($(compgen -f -- "$cur")) local -a files
readarray -t files < <(compgen -f -- "$cur")
COMPREPLY+=("${files[@]}")
fi fi
return return
;; ;;
-3
View File
@@ -171,9 +171,6 @@ NTSTATUS NTAPI NtQuerySystemEnvironmentValueEx(
_Out_opt_ PULONG Attributes // EFI_VARIABLE_* _Out_opt_ PULONG Attributes // EFI_VARIABLE_*
); );
NTSTATUS NTAPI RtlGUIDFromString(IN PCUNICODE_STRING GuidString, OUT GUID* Guid);
NTSTATUS NTAPI RtlStringFromGUIDEx(IN const GUID* Guid, OUT PUNICODE_STRING GuidString, IN BOOLEAN AllocateGuidString);
typedef struct _SYSTEM_SECUREBOOT_INFORMATION { typedef struct _SYSTEM_SECUREBOOT_INFORMATION {
BOOLEAN SecureBootEnabled; BOOLEAN SecureBootEnabled;
BOOLEAN SecureBootCapable; BOOLEAN SecureBootCapable;
+1
View File
@@ -5,6 +5,7 @@
#include <stdalign.h> #include <stdalign.h>
#define FF_EFIVARS_PATH_PREFIX "/sys/firmware/efi/efivars/" #define FF_EFIVARS_PATH_PREFIX "/sys/firmware/efi/efivars/"
#define FF_EFI_GLOBAL_GUID "8be4df61-93ca-11d2-aa0d-00e098032b8c"
const char* ffDetectBootmgr(FFBootmgrResult* result) { const char* ffDetectBootmgr(FFBootmgrResult* result) {
alignas(uint16_t) uint8_t buffer[2048]; alignas(uint16_t) uint8_t buffer[2048];
+6 -4
View File
@@ -39,10 +39,12 @@ const char* ffDetectBootmgr(FFBootmgrResult* result) {
return err; return err;
} }
GUID efiGlobalGuid; GUID efiGlobalGuid = {
if (!NT_SUCCESS(RtlGUIDFromString(&(UNICODE_STRING) RTL_CONSTANT_STRING(L"{" FF_EFI_GLOBAL_GUID L"}"), &efiGlobalGuid))) { .Data1 = 0x8be4df61,
return "RtlGUIDFromString() failed"; .Data2 = 0x93ca,
} .Data3 = 0x11d2,
.Data4 = { 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c },
};
ULONG size = sizeof(result->order); ULONG size = sizeof(result->order);
if (!NT_SUCCESS(NtQuerySystemEnvironmentValueEx(&(UNICODE_STRING) RTL_CONSTANT_STRING(L"BootCurrent"), &efiGlobalGuid, &result->order, &size, nullptr))) { if (!NT_SUCCESS(NtQuerySystemEnvironmentValueEx(&(UNICODE_STRING) RTL_CONSTANT_STRING(L"BootCurrent"), &efiGlobalGuid, &result->order, &size, nullptr))) {
-2
View File
@@ -18,5 +18,3 @@ typedef struct FFEfiLoadOption {
} FFEfiLoadOption; } FFEfiLoadOption;
bool ffEfiFillLoadOption(const FFEfiLoadOption* efiOption, FFBootmgrResult* result); bool ffEfiFillLoadOption(const FFEfiLoadOption* efiOption, FFBootmgrResult* result);
#define FF_EFI_GLOBAL_GUID "8be4df61-93ca-11d2-aa0d-00e098032b8c"
+4 -1
View File
@@ -98,10 +98,12 @@ static const char* detectWithDdcci([[maybe_unused]] FFBrightnessOptions* options
FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libddcutil, ddca_free_any_vcp_value) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libddcutil, ddca_free_any_vcp_value)
FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libddcutil, ddca_close_display) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libddcutil, ddca_close_display)
// libddcutil may print trace messages to stdout during display detection, not only in ddca_init
FF_SUPPRESS_IO();
#ifndef FF_DISABLE_DLOPEN #ifndef FF_DISABLE_DLOPEN
FF_LIBRARY_LOAD_SYMBOL_LAZY(libddcutil, ddca_init) FF_LIBRARY_LOAD_SYMBOL_LAZY(libddcutil, ddca_init)
if (ffddca_init) { if (ffddca_init) {
FF_SUPPRESS_IO();
// Ref: https://github.com/rockowitz/ddcutil/issues/344 // Ref: https://github.com/rockowitz/ddcutil/issues/344
if (ffddca_init(nullptr, -1 /*DDCA_SYSLOG_NOT_SET*/, 1 /*DDCA_INIT_OPTIONS_DISABLE_CONFIG_FILE*/) < 0) { if (ffddca_init(nullptr, -1 /*DDCA_SYSLOG_NOT_SET*/, 1 /*DDCA_INIT_OPTIONS_DISABLE_CONFIG_FILE*/) < 0) {
return "ddca_init() failed"; return "ddca_init() failed";
@@ -148,6 +150,7 @@ static const char* detectWithDdcci([[maybe_unused]] FFBrightnessOptions* options
brightness->max = max; brightness->max = max;
brightness->min = 0; brightness->min = 0;
brightness->current = current; brightness->current = current;
brightness->builtin = false;
ffStrbufInitS(&brightness->name, display->model_name); ffStrbufInitS(&brightness->name, display->model_name);
} }
ffddca_close_display(handle); ffddca_close_display(handle);
+229 -55
View File
@@ -1,67 +1,241 @@
#include "detection/dns/dns.h" #include "detection/dns/dns.h"
#include "common/netif.h" #include "common/netif.h"
#include "common/mallocHelper.h" #include "common/debug.h"
#include "common/windows/registry.h"
#include <ws2tcpip.h> #include <ws2tcpip.h>
#include <iphlpapi.h> #include <iphlpapi.h>
// 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) { const char* ffDetectDNS(FFDNSOptions* options, FFlist* results) {
FF_AUTO_FREE IP_ADAPTER_ADDRESSES* adapter_addresses = nullptr; FF_DEBUG("enter, showType=%u", (unsigned) options->showType);
// The interface that carries the default route is the one the IP stack resolves names through.
// Multiple attempts in case interfaces change while // ffNetifGetDefaultRouteV4() already ignores the interfaces that are not operational.
// we are in the middle of querying them. NET_IFINDEX ifIndex = (NET_IFINDEX) ffNetifGetDefaultRouteV4()->ifIndex;
DWORD adapter_addresses_buffer_size = 0; FF_DEBUG("ifIndex=%u", (unsigned) ifIndex);
for (int attempts = 0;; ++attempts) { if (!ifIndex) {
if (adapter_addresses_buffer_size) { ifIndex = (NET_IFINDEX) ffNetifGetDefaultRouteV6()->ifIndex;
adapter_addresses = (IP_ADAPTER_ADDRESSES*) realloc(adapter_addresses, adapter_addresses_buffer_size); }
assert(adapter_addresses); if (!ifIndex) {
} return "Failed to detect default route";
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";
}
} }
uint32_t defaultRouteIfIndex = ffNetifGetDefaultRouteV4()->ifIndex; NET_LUID luid;
// Iterate through all of the adapters if (ConvertInterfaceIndexToLuid(ifIndex, &luid) != NO_ERROR) {
for (IP_ADAPTER_ADDRESSES* adapter = adapter_addresses; adapter; adapter = adapter->Next) { return "ConvertInterfaceIndexToLuid() failed";
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;
} }
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; return nullptr;
} }
+359 -94
View File
@@ -2,27 +2,269 @@
#include <iphlpapi.h> #include <iphlpapi.h>
#include "common/netif.h" #include "common/netif.h"
#include "common/mallocHelper.h"
#include "common/windows/unicode.h" #include "common/windows/unicode.h"
#include "common/debug.h" #include "common/debug.h"
#include "localip.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[] = { if (flags->length) {
FF_LOCALIP_NIFLAG(DDNS_ENABLED), ffStrbufAppendC(flags, ',');
FF_LOCALIP_NIFLAG(REGISTER_ADAPTER_SUFFIX), }
FF_LOCALIP_NIFLAG(DHCP_ENABLED), ffStrbufAppendS(flags, name);
FF_LOCALIP_NIFLAG(RECEIVE_ONLY), }
FF_LOCALIP_NIFLAG(NO_MULTICAST),
FF_LOCALIP_NIFLAG(IPV6_OTHER_STATEFUL_CONFIG), static void fillInterfaceFlags(FFstrbuf* flags, const MIB_IF_ROW2* row) {
FF_LOCALIP_NIFLAG(NETBIOS_OVER_TCPIP_ENABLED), appendFlag(flags, row->InterfaceAndOperStatusFlags.HardwareInterface, "HARDWARE");
FF_LOCALIP_NIFLAG(IPV4_ENABLED), appendFlag(flags, row->InterfaceAndOperStatusFlags.FilterInterface, "FILTER");
FF_LOCALIP_NIFLAG(IPV6_ENABLED), appendFlag(flags, row->InterfaceAndOperStatusFlags.ConnectorPresent, "CONNECTOR_PRESENT");
FF_LOCALIP_NIFLAG(IPV6_MANAGE_ADDRESS_CONFIG), appendFlag(flags, row->InterfaceAndOperStatusFlags.NotAuthenticated, "NOT_AUTHENTICATED");
// sentinel 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) { const char* ffDetectLocalIps(const FFLocalIpOptions* options, FFlist* results) {
FF_DEBUG("Starting local IP detection with showType=0x%X, namePrefix='%.*s'", 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, (int) options->namePrefix.length,
options->namePrefix.chars); 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 // Every interface is queried with GetIfEntry2() rather than by walking a table of all of them:
// we are in the middle of querying them. // GetIfTable2() costs about 0.95 ms on a machine with a few dozen adapters however few of them
DWORD adapter_addresses_buffer_size = 0; // are reported, because it marshals every registered one, while GetIfEntry2() costs about 24 us
for (int attempts = 0;; ++attempts) { // per interface. With 52 adapters registered and 8 interfaces carrying an address the per
FF_DEBUG("Attempt %d to get adapter addresses, buffer size: %lu", attempts + 1, adapter_addresses_buffer_size); // 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
if (adapter_addresses_buffer_size) { // can pass the filter below anyway.
adapter_addresses = (IP_ADAPTER_ADDRESSES*) realloc(adapter_addresses, adapter_addresses_buffer_size); bool defaultRouteOnly = (options->showType & FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT) != 0;
assert(adapter_addresses); if (defaultRouteOnly) {
if (queryV4) {
FF_DEBUG("Querying the IPv4 default route interface only: ifIndex=%u", (unsigned) ffNetifGetDefaultRouteV4()->ifIndex);
} }
if (queryV6) {
DWORD family = options->showType & FF_LOCALIP_TYPE_IPV4_BIT FF_DEBUG("Querying the IPv6 default route interface only: ifIndex=%u", (unsigned) ffNetifGetDefaultRouteV6()->ifIndex);
? 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";
} }
} }
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; [[maybe_unused]] int adapterCount = 0, processedCount = 0;
// Iterate through all of the adapters // Rows of the same interface are not adjacent (IPv4 rows come first),
for (IP_ADAPTER_ADDRESSES* adapter = adapter_addresses; adapter; adapter = adapter->Next) { // 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++; 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", FF_DEBUG("Processing adapter %d: IfIndex=%u, IfType=%u, OperStatus=%u",
adapterCount, adapterCount,
(unsigned) adapter->IfIndex, (unsigned) ifIndex,
(unsigned) adapter->IfType, (unsigned) ifRow.Type,
(unsigned) adapter->OperStatus); (unsigned) ifRow.OperStatus);
if (adapter->OperStatus != IfOperStatusUp) { if (ifRow.OperStatus != IfOperStatusUp) {
FF_DEBUG("Skipping adapter %u (not operational, status=%d)", (unsigned) adapter->IfIndex, adapter->OperStatus); FF_DEBUG("Skipping adapter %u (not operational, status=%d)", (unsigned) ifIndex, ifRow.OperStatus);
continue; continue;
} }
bool isLoop = adapter->IfType == IF_TYPE_SOFTWARE_LOOPBACK; bool isLoop = ifRow.Type == IF_TYPE_SOFTWARE_LOOPBACK;
FF_DEBUG("Adapter %u: isLoopback=%s", (unsigned) adapter->IfIndex, isLoop ? "true" : "false"); FF_DEBUG("Adapter %u: isLoopback=%s", (unsigned) ifIndex, isLoop ? "true" : "false");
if (isLoop && !(options->showType & FF_LOCALIP_TYPE_LOOP_BIT)) { 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; continue;
} }
FF_STRBUF_AUTO_DESTROY name = ffStrbufCreateWS(adapter->FriendlyName); FF_STRBUF_AUTO_DESTROY name = ffStrbufCreateWS(ifRow.Alias);
FF_DEBUG("Adapter %u name: '%s'", (unsigned) adapter->IfIndex, name.chars); FF_DEBUG("Adapter %u name: '%s'", (unsigned) ifIndex, name.chars);
if (options->namePrefix.length && !ffStrbufStartsWith(&name, &options->namePrefix)) { if (options->namePrefix.length && !ffStrbufStartsWith(&name, &options->namePrefix)) {
FF_DEBUG("Skipping adapter %u (name doesn't match prefix '%.*s')", FF_DEBUG("Skipping adapter %u (name doesn't match prefix '%.*s')",
(unsigned) adapter->IfIndex, (unsigned) ifIndex,
(int) options->namePrefix.length, (int) options->namePrefix.length,
options->namePrefix.chars); options->namePrefix.chars);
continue; 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++; 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); FFLocalIpResult* item = FF_LIST_ADD(FFLocalIpResult, *results);
ffStrbufInitMove(&item->name, &name); ffStrbufInitMove(&item->name, &name);
@@ -124,16 +379,26 @@ const char* ffDetectLocalIps(const FFLocalIpOptions* options, FFlist* results) {
item->speed = -1; item->speed = -1;
item->mtu = -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); 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; [[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", FF_DEBUG("Processing unicast address: prefix origin=%d, suffix origin=%d, family=%d, DadState=%d",
ifa->PrefixOrigin, ifa->PrefixOrigin,
ifa->SuffixOrigin, ifa->SuffixOrigin,
ifa->Address.lpSockaddr->sa_family, ifa->Address.si_family,
ifa->DadState); ifa->DadState);
if (!(options->showType & FF_LOCALIP_TYPE_ALL_IPS_BIT)) { 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 // 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))) { 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); FF_DEBUG("Skipping IPv4 address (not requested in typesToAdd=0x%X)", typesToAdd);
continue; 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) { if (defaultRouteOnly && !isDefaultRoute) {
FF_DEBUG("Skipping IPv4 address (not on default route interface)"); FF_DEBUG("Skipping IPv4 address (not on default route interface)");
continue; continue;
} }
SOCKADDR_IN* ipv4 = (SOCKADDR_IN*) ifa->Address.lpSockaddr; SOCKADDR_IN* ipv4 = &ifa->Address.Ipv4;
char addressBuffer[INET_ADDRSTRLEN + 10]; char addressBuffer[INET_ADDRSTRLEN + 10];
char* end = RtlIpv4AddressToStringA(&ipv4->sin_addr, addressBuffer); char* end = RtlIpv4AddressToStringA(&ipv4->sin_addr, addressBuffer);
@@ -185,13 +450,13 @@ const char* ffDetectLocalIps(const FFLocalIpOptions* options, FFlist* results) {
if (typesToAdd == 0) { if (typesToAdd == 0) {
break; 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))) { 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); FF_DEBUG("Skipping IPv6 address (not requested in typesToAdd=0x%X)", typesToAdd);
continue; continue;
} }
SOCKADDR_IN6* ipv6 = (SOCKADDR_IN6*) ifa->Address.lpSockaddr; SOCKADDR_IN6* ipv6 = &ifa->Address.Ipv6;
FFLocalIpIpv6Type ipv6Type = FF_LOCALIP_IPV6_TYPE_NONE; FFLocalIpIpv6Type ipv6Type = FF_LOCALIP_IPV6_TYPE_NONE;
if (IN6_IS_ADDR_GLOBAL(&ipv6->sin6_addr)) { if (IN6_IS_ADDR_GLOBAL(&ipv6->sin6_addr)) {
@@ -209,8 +474,8 @@ const char* ffDetectLocalIps(const FFLocalIpOptions* options, FFlist* results) {
continue; 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) { if (defaultRouteOnly && !isDefaultRoute) {
FF_DEBUG("Skipping IPv6 address (not on default route interface)"); FF_DEBUG("Skipping IPv6 address (not on default route interface)");
continue; 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) { if (options->showType & FF_LOCALIP_TYPE_SPEED_BIT) {
item->speed = (int32_t) (adapter->ReceiveLinkSpeed / 1000000); item->speed = (int32_t) (ifRow.ReceiveLinkSpeed / 1000000);
FF_DEBUG("Adapter %u speed: %d Mbps (raw: %llu)", (unsigned) adapter->IfIndex, item->speed, adapter->ReceiveLinkSpeed); FF_DEBUG("Adapter %u speed: %d Mbps (raw: %llu)", (unsigned) ifIndex, item->speed, ifRow.ReceiveLinkSpeed);
} }
if (options->showType & FF_LOCALIP_TYPE_MTU_BIT) { if (options->showType & FF_LOCALIP_TYPE_MTU_BIT) {
item->mtu = (int32_t) adapter->Mtu; item->mtu = (int32_t) ifRow.Mtu;
FF_DEBUG("Adapter %u MTU: %d", (unsigned) adapter->IfIndex, item->mtu); FF_DEBUG("Adapter %u MTU: %d", (unsigned) ifIndex, item->mtu);
} }
if (options->showType & FF_LOCALIP_TYPE_FLAGS_BIT) { if (options->showType & FF_LOCALIP_TYPE_MAC_BIT && ifRow.PhysicalAddressLength == 6) {
ffLocalIpFillNIFlags(&item->flags, adapter->Flags, niFlagOptions); const uint8_t* ptr = ifRow.PhysicalAddress;
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;
ffStrbufSetF(&item->mac, "%02x:%02x:%02x:%02x:%02x:%02x", ptr[0], ptr[1], ptr[2], ptr[3], ptr[4], ptr[5]); 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, processedCount,
results->length); results->length);
return nullptr; cleanup:
if (table) {
FreeMibTable(table);
}
return errorMessage;
} }
+63 -55
View File
@@ -1,71 +1,79 @@
#include "netio.h" #include "netio.h"
#include "common/netif.h" #include "common/netif.h"
#include "common/mallocHelper.h"
#include "common/windows/unicode.h" #include "common/windows/unicode.h"
#include <ws2tcpip.h> #include <ws2tcpip.h>
#include <iphlpapi.h> #include <iphlpapi.h>
const char* ffNetIOGetIoCounters(FFlist* result, FFNetIOOptions* options) { static void addInterface(FFlist* result, const FFNetIOOptions* options, NET_IFINDEX ifIndex, bool isDefaultRoute) {
FF_AUTO_FREE IP_ADAPTER_ADDRESSES* adapter_addresses = nullptr; MIB_IF_ROW2 ifRow = { .InterfaceIndex = ifIndex };
if (!NETIO_SUCCESS(GetIfEntry2(&ifRow))) {
// Multiple attempts in case interfaces change while return;
// 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";
}
} }
// 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; uint32_t defaultRouteIfIndex = ffNetifGetDefaultRouteV4()->ifIndex;
// Iterate through all of the adapters // With `defaultRouteOnly` (the default) the interface carrying the default route is the only one
for (IP_ADAPTER_ADDRESSES* adapter = adapter_addresses; adapter; adapter = adapter->Next) { // that can be reported, so it is queried with the single entry API instead of walking a table of
bool isDefaultRoute = adapter->IfIndex == defaultRouteIfIndex; // every interface: GetIfEntry2() on one interface costs about 12 us.
if (options->defaultRouteOnly && !isDefaultRoute) { if (options->defaultRouteOnly) {
continue; addInterface(result, options, (NET_IFINDEX) defaultRouteIfIndex, true);
} return nullptr;
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,
};
}
} }
// 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; return nullptr;
} }
+65 -1
View File
@@ -70,7 +70,71 @@ bool ffPackagesWriteCache(FFstrbuf* cacheDir, FFstrbuf* cacheContent, uint32_t n
return ffWriteFileBuffer(cacheDir->chars, cacheContent); return ffWriteFileBuffer(cacheDir->chars, cacheContent);
} }
#ifndef _WIN32 #if __linux__
#include <sys/syscall.h>
struct linux_dirent64 {
uint64_t d_ino; /* 64-bit inode number */
int64_t d_off; /* Not an offset; see getdents() */
unsigned short d_reclen; /* Size of this dirent */
unsigned char d_type; /* File type */
char d_name[]; /* Filename (null-terminated) */
};
uint32_t ffPackagesGetNumElements(const char* dirname, bool isdir) {
FF_AUTO_CLOSE_FD int fd = open(dirname, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
if (fd < 0) {
return 0;
}
alignas(struct linux_dirent64) uint8_t bytes[64 * 1024];
uint32_t num_elements = 0;
const size_t nameOffset = offsetof(struct linux_dirent64, d_name);
for (;;) {
long bytesRead = syscall(SYS_getdents64, fd, bytes, sizeof(bytes));
if (bytesRead < 0) {
if (errno == EINTR) {
continue;
}
break;
}
if (bytesRead == 0) {
break;
}
size_t remaining = (size_t) bytesRead;
struct linux_dirent64* entry = (struct linux_dirent64*) bytes;
while (remaining >= nameOffset + 1) {
bool ok = false;
if (entry->d_name[0] != '.') {
if (__builtin_expect(entry->d_type != DT_UNKNOWN && entry->d_type != DT_LNK, true)) {
ok = entry->d_type == (isdir ? DT_DIR : DT_REG);
} else {
struct stat stbuf;
if (fstatat(fd, entry->d_name, &stbuf, 0) == 0) {
ok = isdir ? S_ISDIR(stbuf.st_mode) : S_ISREG(stbuf.st_mode);
}
}
}
num_elements += ok;
size_t recordLength = entry->d_reclen;
remaining -= recordLength;
entry = (struct linux_dirent64*) ((uint8_t*) entry + recordLength);
}
if (remaining != 0) {
break;
}
}
return num_elements;
}
#elif !_WIN32
uint32_t ffPackagesGetNumElements(const char* dirname, bool isdir) { uint32_t ffPackagesGetNumElements(const char* dirname, bool isdir) {
FF_AUTO_CLOSE_DIR DIR* dirp = opendir(dirname); FF_AUTO_CLOSE_DIR DIR* dirp = opendir(dirname);
if (dirp == nullptr) { if (dirp == nullptr) {
+2
View File
@@ -19,6 +19,8 @@ static inline void applyVulkanVersion(uint32_t vulkanVersion, FFVersion* ffVersi
} }
static void applyDriverName(VkPhysicalDeviceDriverPropertiesKHR* properties, FFstrbuf* result) { static void applyDriverName(VkPhysicalDeviceDriverPropertiesKHR* properties, FFstrbuf* result) {
ffStrbufClear(result);
if (!ffStrSet(properties->driverName)) { if (!ffStrSet(properties->driverName)) {
return; return;
} }