[PATCH v2 0/1] MR10553: iphlpapi: Prefer adapters with valid MAC in GetAdaptersInfo.
### What this changes - Adds a MAC validity predicate for adapter entries. - In GetAdaptersInfo, counts adapters with valid physical MAC. - If valid adapters exist, excludes entries with invalid MAC from returned list. ### Why On macOS, pseudo interfaces without usable physical address can appear before real hardware adapters. Legacy clients that pick the first adapter may send empty MAC and fail auth. ### Scope - Module: iphlpapi - API path: GetAdaptersInfo - Platform impact: mainly macOS-like interface layouts with pseudo/tunnel adapters exposed early. Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=59614 -- v2: nsiproxy.sys: Sort adapters to prioritize physical interfaces. https://gitlab.winehq.org/wine/wine/-/merge_requests/10553
From: Nikolai Abdusamatov <nick.luft.work@gmail.com> Sort the adapter list so Ethernet and Wi-Fi interfaces with valid MAC addresses appear before other adapters, followed by tunnel interfaces and loopback. On macOS, tunnel-like interfaces are classified as IF_TYPE_TUNNEL to match Windows behavior. Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=59614 --- dlls/nsiproxy.sys/ndis.c | 90 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 88 insertions(+), 2 deletions(-) diff --git a/dlls/nsiproxy.sys/ndis.c b/dlls/nsiproxy.sys/ndis.c index 8c15186ed53..96bea9b77eb 100644 --- a/dlls/nsiproxy.sys/ndis.c +++ b/dlls/nsiproxy.sys/ndis.c @@ -124,6 +124,35 @@ static struct if_entry *find_entry_from_luid( const NET_LUID *luid ) return NULL; } +static BOOL if_phys_addr_empty( const IF_PHYSICAL_ADDRESS *phys_addr ) +{ + UINT i; + + if (!phys_addr->Length) return TRUE; + for (i = 0; i < phys_addr->Length; i++) + if (phys_addr->Address[i]) return FALSE; + return TRUE; +} + +static UINT if_entry_rank( const struct if_entry *entry ) +{ + if ((entry->if_type == MIB_IF_TYPE_ETHERNET || entry->if_type == IF_TYPE_IEEE80211) && + !if_phys_addr_empty( &entry->if_phys_addr )) + return 0; + if (entry->if_type == IF_TYPE_TUNNEL) return 2; + if (entry->if_type == MIB_IF_TYPE_LOOPBACK) return 3; + return 1; +} + +static BOOL if_entry_before( const struct if_entry *entry, const struct if_entry *other ) +{ + UINT rank = if_entry_rank( entry ); + UINT other_rank = if_entry_rank( other ); + + if (rank != other_rank) return rank < other_rank; + return entry->if_index < other->if_index; +} + #if defined (SIOCGIFHWADDR) && defined (HAVE_STRUCT_IFREQ_IFR_HWADDR) static NTSTATUS if_get_physical( const char *name, UINT *type, IF_PHYSICAL_ADDRESS *phys_addr ) { @@ -274,9 +303,46 @@ static WCHAR *strdupAtoW( const char *str ) return ret; } -static struct if_entry *add_entry( UINT index, char *name ) +#ifdef SIOCGIFFLAGS +static BOOL if_is_tunnel_like( int fd, const char *name, const IF_PHYSICAL_ADDRESS *phys_addr, UINT type ) +{ + int name_len; + struct ifreq ifr; + BOOL ret = FALSE; + + if (type == MIB_IF_TYPE_LOOPBACK || type == MIB_IF_TYPE_PPP || phys_addr->Length) return FALSE; + + name_len = strlen( name ) + 1; + if (name_len > sizeof(ifr.ifr_name)) return FALSE; + if (fd == -1) return FALSE; + + memset( &ifr, 0, sizeof(ifr) ); + memcpy( ifr.ifr_name, name, name_len ); + + if (!ioctl( fd, SIOCGIFFLAGS, &ifr )) + { + if ((ifr.ifr_flags & IFF_POINTOPOINT) || + (type == MIB_IF_TYPE_OTHER && !(ifr.ifr_flags & IFF_BROADCAST))) + ret = TRUE; + } + + return ret; +} +#else +static BOOL if_is_tunnel_like( int fd, const char *name, const IF_PHYSICAL_ADDRESS *phys_addr, UINT type ) +{ + (void)fd; + (void)name; + (void)phys_addr; + (void)type; + return FALSE; +} +#endif + +static struct if_entry *add_entry( UINT index, char *name, int fd ) { struct if_entry *entry; + struct if_entry *iter; int name_len = strlen( name ); if (name_len >= sizeof(entry->if_unix_name)) return NULL; @@ -293,6 +359,8 @@ static struct if_entry *add_entry( UINT index, char *name ) } if_get_physical( name, &entry->if_type, &entry->if_phys_addr ); + if (if_is_tunnel_like( fd, name, &entry->if_phys_addr, entry->if_type )) + entry->if_type = IF_TYPE_TUNNEL; entry->if_luid.Info.Reserved = 0; entry->if_luid.Info.NetLuidIndex = index; @@ -302,6 +370,15 @@ static struct if_entry *add_entry( UINT index, char *name ) entry->if_guid.Data1 = index; memcpy( entry->if_guid.Data4 + 2, "NetDev", 6 ); + LIST_FOR_EACH_ENTRY( iter, &if_list, struct if_entry, entry ) + { + if (if_entry_before( entry, iter )) + { + list_add_before( &iter->entry, &entry->entry ); + return entry; + } + } + list_add_tail( &if_list, &entry->entry ); return entry; } @@ -310,13 +387,22 @@ static unsigned int update_if_table( void ) { struct if_nameindex *indices = if_nameindex(), *entry; unsigned int append_count = 0; +#ifdef SIOCGIFFLAGS + int fd = socket( PF_INET, SOCK_DGRAM, 0 ); +#else + int fd = -1; +#endif for (entry = indices; entry->if_index; entry++) { - if (!find_entry_from_index( entry->if_index ) && add_entry( entry->if_index, entry->if_name )) + if (!find_entry_from_index( entry->if_index ) && add_entry( entry->if_index, entry->if_name, fd )) ++append_count; } +#ifdef SIOCGIFFLAGS + if (fd != -1) close( fd ); +#endif + if_freenameindex( indices ); return append_count; } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10553
On Mon Apr 6 16:19:29 2026 +0000, Paul Gofman wrote:
Won't that filter out loopback adapters? Also, I have wireguard interface here, it also doesn't have MAC address, maybe there are more cases. Such sort of filtering, if done correctly, should be performed in nsiproxy.sys and not in iphlpapi. I hope there should be a better way to detect the adapters we are not interested in (possibly Mac specific, there is a lot of Mac specific handling in nsirpoxy.sys). Another possibility is that those pseudo adapters should not be filtered out but maybe instead sorted properly so they don't appear first. This should be confirmed on Windows how such pseudo adapters looks like and if that is the sort order and not their presence which is actually different from Windows. If there is no obvious difference another potential possibility is that the affected app is also broken on Windows if VPN adapters are present and we don't have anything to fix here. Thanks for the review. This patch ecae98b8c11f56042794290e83ffae30ef98de2a doesn't filter out loopback, WireGuard, or other pseudo adapters - it only reorders the list so physical Ethernet and Wi-Fi adapters with a valid MAC come first.
On macOS, the current backend order follows the system `if_index`, so tunnel interfaces like `gif0` and `stf0` can appear before physical adapters. The change here is to normalize the order so physical adapters are placed first, which matches the order I observed on Windows. I also investigated the Aion 4.8 case. The client calls `GetAdaptersInfo` with a fixed buffer of 11264 bytes. On a system with 25 interfaces, the API returns `ERROR_BUFFER_OVERFLOW` and reports that 17088 bytes are needed. The client does not retry, so it never gets a valid adapter list and falls back to an empty MAC string. That is why the server sees an empty MAC. The issue here is that the app never receives a usable adapter list on this path. The sorting change helps apps that take the first adapter from `GetAdaptersInfo`, but it does not change the fixed-buffer overflow behavior in Aion. I'll also attach the logs from before and after these changes. [ndis_clean.txt](/uploads/6a96683e4b2e68ef9f9014df42d9ad55/ndis_clean.txt) [ndis_changes.txt](/uploads/94ecfe0943169b81ea0f8dd8015cde4d/ndis_changes.txt) -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10553#note_135127
On Mon Apr 6 16:19:29 2026 +0000, Nikolai Abdusamatov wrote:
Thanks for the review. This patch ecae98b8c11f56042794290e83ffae30ef98de2a doesn't filter out loopback, WireGuard, or other pseudo adapters - it only reorders the list so physical Ethernet and Wi-Fi adapters with a valid MAC come first. On macOS, the current backend order follows the system `if_index`, so tunnel interfaces like `gif0` and `stf0` can appear before physical adapters. The change here is to normalize the order so physical adapters are placed first, which matches the order I observed on Windows. I also investigated the Aion 4.8 case. The client calls `GetAdaptersInfo` with a fixed buffer of 11264 bytes. On a system with 25 interfaces, the API returns `ERROR_BUFFER_OVERFLOW` and reports that 17088 bytes are needed. The client does not retry, so it never gets a valid adapter list and falls back to an empty MAC string. That is why the server sees an empty MAC. The issue here is that the app never receives a usable adapter list on this path. The sorting change helps apps that take the first adapter from `GetAdaptersInfo`, but it does not change the fixed-buffer overflow behavior in Aion. I'll also attach the logs from before and after these changes. [ndis_clean.txt](/uploads/6a96683e4b2e68ef9f9014df42d9ad55/ndis_clean.txt) [ndis_changes.txt](/uploads/94ecfe0943169b81ea0f8dd8015cde4d/ndis_changes.txt) This needs checks on Windows. I did one, enabling Proton VPN. I am attaching the patch which adds a bit more of info to GetAdaptersInfo test output and the output from up to date Windows 11.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10553#note_135129
On Mon Apr 6 16:41:15 2026 +0000, Paul Gofman wrote:
This needs checks on Windows. I did one, enabling Proton VPN. I am attaching the patch which adds a bit more of info to GetAdaptersInfo test output and the output from up to date Windows 11. [test.patch](/uploads/6b62a42e1997d6621d7a9e499e522999/test.patch)
[out.txt](/uploads/a83be2f1939cfc8784e1b9c20d7a1a01/out.txt) That test shows that: * adapters with 0 HW id are possible on Windows; * GetAdaptersInfo on Windows **does not sort by that** and the 0 HW address does not appear last. So it looks like the change is not correct. Also, the change affects the most basic NPI_MS_NDIS_MODULEID table which is used in many places, not only in GetAdaptersInfo, and changing the sort order of that one requires more tests and, first of all, directly testing the output from NsiAllocateAndGetTable( &NPI_MS_NDIS_MODULEID ). When I was mentioning doing things in nsiproxy.sys I was referring specifically to filtering out the interfaces. The sorting could maybe be done in iphlpapi (like we do in GetAdaptersAddresses) but that needs to be backed by some tests and my ad-hoc tests shows that the suggested sorting is incorrect either way. So the proper fix needs to be somehow different. I am not versed with Macs, what are those lo0, gif0 interfaces exactly which go first? Do we need those? Maybe if we don't we should still filter those out in nsiproxy.sys (the direction initial patch suggested), just based not on HW address but based on some other attribute(s) MAC specific, and in nsiproxy.sys and not iphlpapi. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10553#note_135130
On Mon Apr 6 16:50:33 2026 +0000, Paul Gofman wrote:
[test.patch](/uploads/6b62a42e1997d6621d7a9e499e522999/test.patch) [out.txt](/uploads/a83be2f1939cfc8784e1b9c20d7a1a01/out.txt) That test shows that: * adapters with 0 HW id are possible on Windows; * GetAdaptersInfo on Windows **does not sort by that** and the 0 HW address does not appear last. So it looks like the change is not correct. Also, the change affects the most basic NPI_MS_NDIS_MODULEID table which is used in many places, not only in GetAdaptersInfo, and changing the sort order of that one requires more tests and, first of all, directly testing the output from NsiAllocateAndGetTable( &NPI_MS_NDIS_MODULEID ). When I was mentioning doing things in nsiproxy.sys I was referring specifically to filtering out the interfaces. The sorting could maybe be done in iphlpapi (like we do in GetAdaptersAddresses) but that needs to be backed by some tests and my ad-hoc tests shows that the suggested sorting is incorrect either way. So the proper fix needs to be somehow different. I am not versed with Macs, what are those lo0, gif0 interfaces exactly which go first? Do we need those? Maybe if we don't we should still filter those out in nsiproxy.sys (the direction initial patch suggested), just based not on HW address but based on some other attribute(s) MAC specific, and in nsiproxy.sys and not iphlpapi. Regarding your question about interfaces: `gif0` and `stf0` are macOS tunnel interfaces (Generic Tunnel and 6to4). The `utun*` interfaces are used by macOS for system and third-party VPNs.
These are macOS-specific interfaces that don't exist in Windows (as far as I can tell). Filtering them out in `nsiproxy.sys` would make the list of adapters more similar to what Windows would return on equivalent hardware, and would also reduce the likelihood that applications would encounter buffer size issues, given the typical number of adapters in Windows. I also considered filtering out these macOS-specific interfaces in `nsiproxy.sys`, but couldn’t find a reliable way to identify them. Hard-coding interface names such as gif0, stf0, and utun seems unreliable, and I don’t know of a better macOS-specific attribute that could be used. Maybe someone knows of one? -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10553#note_135159
participants (3)
-
Nikolai Abdusamatov -
Nikolai Abdusamatov (@n-abdusamatov) -
Paul Gofman (@gofman)