feat(libc): report getifaddrs errors through errno and handle Windows edge cases

Return -1 with the FreeBSD errno for a null list and for expected host and allocation failures, as getcwd does. On Windows, give an unspecified prefix length a host mask, leave PPP and tunnel adapters without broadcast or point-to-point flags since their peer address is not reported, fall back to if<index> for long NDIS names, and bound the adapter query retries. Compare every entry with the host's own list in the test.
This commit is contained in:
LuisBiceps 2026-10-09 03:19:46 +08:00 committed by Adrià Franch
parent 7cd185ec79
commit a09b67b142
3 changed files with 172 additions and 93 deletions

View File

@ -14,6 +14,7 @@
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <new>
#include <stdexcept>
#include <string>
#include <utility>
@ -91,7 +92,7 @@ std::size_t Aligned(std::size_t size) {
#ifdef _WIN32
std::vector<std::uint8_t> PrefixMask(int family, unsigned prefix) {
const unsigned bits = family == AF_INET ? 32 : 128;
if (prefix > bits) throw std::runtime_error("getifaddrs: host prefix length " + std::to_string(prefix) + " is out of range");
if (prefix > bits) prefix = bits;
std::uint8_t mask[16] = {};
for (unsigned bit = 0; bit < prefix; ++bit) mask[bit / 8] |= static_cast<std::uint8_t>(0x80 >> (bit % 8));
return family == AF_INET ? GuestIpv4(mask) : GuestIpv6(mask, 0);
@ -108,8 +109,7 @@ std::uint32_t InterfaceFlags(const IP_ADAPTER_ADDRESSES* adapter) {
std::uint32_t flags = 0;
if (adapter->OperStatus == IfOperStatusUp) flags |= guestUp | guestRunning;
if (adapter->IfType == IF_TYPE_SOFTWARE_LOOPBACK) flags |= guestLoopback;
else if (adapter->IfType == IF_TYPE_PPP || adapter->IfType == IF_TYPE_TUNNEL) flags |= guestPointToPoint;
else flags |= guestBroadcast;
else if (adapter->IfType != IF_TYPE_PPP && adapter->IfType != IF_TYPE_TUNNEL) flags |= guestBroadcast;
if (!(adapter->Flags & IP_ADAPTER_NO_MULTICAST)) flags |= guestMulticast;
return flags;
}
@ -118,21 +118,23 @@ std::string InterfaceName(const IP_ADAPTER_ADDRESSES* adapter) {
char name[NDIS_IF_MAX_STRING_SIZE + 1] = {};
const auto status = ConvertInterfaceLuidToNameA(&adapter->Luid, name, sizeof(name));
if (status != NO_ERROR) throw std::runtime_error("getifaddrs: ConvertInterfaceLuidToNameA failed with " + std::to_string(status));
return CheckedName(name);
if (std::strlen(name) < guestNameCapacity) return CheckedName(name);
return "if" + std::to_string(adapter->IfIndex ? adapter->IfIndex : adapter->Ipv6IfIndex);
}
std::vector<Entry> HostEntries() {
int HostEntries(std::vector<Entry>& entries) {
const ULONG options = GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER;
ULONG size = 0;
std::vector<std::uint8_t> storage;
ULONG result = GetAdaptersAddresses(AF_UNSPEC, options, nullptr, nullptr, &size);
while (result == ERROR_BUFFER_OVERFLOW) {
for (unsigned calls = 1; result == ERROR_BUFFER_OVERFLOW && calls < 4; ++calls) {
storage.resize(size);
result = GetAdaptersAddresses(AF_UNSPEC, options, nullptr, reinterpret_cast<IP_ADAPTER_ADDRESSES*>(storage.data()), &size);
}
if (result == ERROR_NO_DATA) return {};
if (result == ERROR_NO_DATA) return 0;
if (result == ERROR_BUFFER_OVERFLOW) { *__error_nid_postfix() = 55; return -1; }
if (result == ERROR_NOT_ENOUGH_MEMORY) { *__error_nid_postfix() = 12; return -1; }
if (result != NO_ERROR) throw std::runtime_error("getifaddrs: GetAdaptersAddresses failed with " + std::to_string(result));
std::vector<Entry> entries;
for (auto* adapter = reinterpret_cast<IP_ADAPTER_ADDRESSES*>(storage.data()); adapter; adapter = adapter->Next) {
std::string name;
for (auto* unicast = adapter->FirstUnicastAddress; unicast; unicast = unicast->Next) {
@ -145,7 +147,7 @@ std::vector<Entry> HostEntries() {
entries.push_back(std::move(entry));
}
}
return entries;
return 0;
}
#else
std::uint32_t InterfaceFlags(unsigned int host) {
@ -160,10 +162,18 @@ std::uint32_t InterfaceFlags(unsigned int host) {
return guest;
}
std::vector<Entry> HostEntries() {
int HostEntries(std::vector<Entry>& entries) {
ifaddrs* host = nullptr;
if (::getifaddrs(&host) != 0) throw std::runtime_error(std::string("getifaddrs: host enumeration failed: ") + std::strerror(errno));
std::vector<Entry> entries;
if (::getifaddrs(&host) != 0) {
const int hostError = errno;
switch (hostError) {
case ENOMEM: *__error_nid_postfix() = 12; return -1;
case ENOBUFS: *__error_nid_postfix() = 55; return -1;
case EMFILE: *__error_nid_postfix() = 24; return -1;
case ENFILE: *__error_nid_postfix() = 23; return -1;
default: throw std::runtime_error(std::string("getifaddrs: host enumeration failed: ") + std::strerror(hostError));
}
}
try {
for (auto* item = host; item; item = item->ifa_next) {
if (!item->ifa_addr || (item->ifa_addr->sa_family != AF_INET && item->ifa_addr->sa_family != AF_INET6)) continue;
@ -178,7 +188,7 @@ std::vector<Entry> HostEntries() {
throw;
}
::freeifaddrs(host);
return entries;
return 0;
}
#endif
}
@ -186,41 +196,44 @@ std::vector<Entry> HostEntries() {
extern "C" {
int APS5_VABI getifaddrs_nid_postfix(GuestInterfaceAddress** list) {
if (!list) throw std::invalid_argument("getifaddrs: null list");
const std::vector<Entry> entries = HostEntries();
if (entries.empty()) {
*list = nullptr;
if (!list) { *__error_nid_postfix() = 14; return -1; }
try {
std::vector<Entry> entries;
if (HostEntries(entries) != 0) return -1;
if (entries.empty()) {
*list = nullptr;
return 0;
}
std::size_t size = entries.size() * sizeof(GuestInterfaceAddress);
for (const auto& entry : entries)
size += Aligned(entry.name.size() + 1) + Aligned(entry.address.size()) + Aligned(entry.netmask.size()) + Aligned(entry.destination.size());
auto* block = static_cast<std::uint8_t*>(std::malloc(size));
if (!block) {
*__error_nid_postfix() = 12;
return -1;
}
auto* items = reinterpret_cast<GuestInterfaceAddress*>(block);
std::size_t offset = entries.size() * sizeof(GuestInterfaceAddress);
const auto place = [&](const void* source, std::size_t length) {
void* target = block + offset;
std::memcpy(target, source, length);
offset += Aligned(length);
return target;
};
for (std::size_t index = 0; index < entries.size(); ++index) {
const Entry& entry = entries[index];
GuestInterfaceAddress& item = items[index];
item.next = index + 1 < entries.size() ? &items[index + 1] : nullptr;
item.name = static_cast<char*>(place(entry.name.c_str(), entry.name.size() + 1));
item.flags = entry.flags;
item.address = place(entry.address.data(), entry.address.size());
item.netmask = place(entry.netmask.data(), entry.netmask.size());
item.destination = entry.destination.empty() ? nullptr : place(entry.destination.data(), entry.destination.size());
item.data = nullptr;
}
*list = items;
return 0;
}
std::size_t size = entries.size() * sizeof(GuestInterfaceAddress);
for (const auto& entry : entries)
size += Aligned(entry.name.size() + 1) + Aligned(entry.address.size()) + Aligned(entry.netmask.size()) + Aligned(entry.destination.size());
auto* block = static_cast<std::uint8_t*>(std::malloc(size));
if (!block) {
*__error_nid_postfix() = 12;
return -1;
}
auto* items = reinterpret_cast<GuestInterfaceAddress*>(block);
std::size_t offset = entries.size() * sizeof(GuestInterfaceAddress);
const auto place = [&](const void* source, std::size_t length) {
void* target = block + offset;
std::memcpy(target, source, length);
offset += Aligned(length);
return target;
};
for (std::size_t index = 0; index < entries.size(); ++index) {
const Entry& entry = entries[index];
GuestInterfaceAddress& item = items[index];
item.next = index + 1 < entries.size() ? &items[index + 1] : nullptr;
item.name = static_cast<char*>(place(entry.name.c_str(), entry.name.size() + 1));
item.flags = entry.flags;
item.address = place(entry.address.data(), entry.address.size());
item.netmask = place(entry.netmask.data(), entry.netmask.size());
item.destination = entry.destination.empty() ? nullptr : place(entry.destination.data(), entry.destination.size());
item.data = nullptr;
}
*list = items;
return 0;
} catch (const std::bad_alloc&) { *__error_nid_postfix() = 12; return -1; }
}
void APS5_VABI freeifaddrs_nid_postfix(GuestInterfaceAddress* list) {

View File

@ -12,7 +12,6 @@
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <stdexcept>
#include <vector>
struct GuestInterfaceAddress {
@ -25,78 +24,145 @@ struct GuestInterfaceAddress {
void* data;
};
static_assert(sizeof(GuestInterfaceAddress) == 56);
static_assert(offsetof(GuestInterfaceAddress, flags) == 16);
static_assert(offsetof(GuestInterfaceAddress, address) == 24);
static_assert(offsetof(GuestInterfaceAddress, netmask) == 32);
static_assert(offsetof(GuestInterfaceAddress, destination) == 40);
static_assert(offsetof(GuestInterfaceAddress, data) == 48);
extern "C" {
int APS5_VABI getifaddrs_nid_postfix(GuestInterfaceAddress** list);
void APS5_VABI freeifaddrs_nid_postfix(GuestInterfaceAddress* list);
int* APS5_VABI __error_nid_postfix();
}
static void Require(bool value) { if (!value) std::abort(); }
static std::size_t HostIpv4Count() {
std::size_t count = 0;
struct HostInterfaceAddress {
int family;
std::uint8_t address[16];
std::uint8_t netmask[16];
std::uint32_t scope;
};
static HostInterfaceAddress HostAddress(const sockaddr* address) {
HostInterfaceAddress entry{};
entry.family = address->sa_family;
if (entry.family == AF_INET) {
std::memcpy(entry.address, &reinterpret_cast<const sockaddr_in*>(address)->sin_addr, 4);
} else {
const auto* v6 = reinterpret_cast<const sockaddr_in6*>(address);
std::memcpy(entry.address, &v6->sin6_addr, 16);
entry.scope = v6->sin6_scope_id;
}
return entry;
}
static std::vector<HostInterfaceAddress> HostSnapshot() {
std::vector<HostInterfaceAddress> entries;
#ifdef _WIN32
const ULONG options = GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER;
ULONG size = 0;
std::vector<std::uint8_t> storage;
ULONG result = GetAdaptersAddresses(AF_UNSPEC, options, nullptr, nullptr, &size);
while (result == ERROR_BUFFER_OVERFLOW) {
for (unsigned calls = 1; result == ERROR_BUFFER_OVERFLOW && calls < 4; ++calls) {
storage.resize(size);
result = GetAdaptersAddresses(AF_UNSPEC, options, nullptr, reinterpret_cast<IP_ADAPTER_ADDRESSES*>(storage.data()), &size);
}
if (result == ERROR_NO_DATA) return entries;
Require(result == NO_ERROR);
for (auto* adapter = reinterpret_cast<IP_ADAPTER_ADDRESSES*>(storage.data()); adapter; adapter = adapter->Next)
for (auto* unicast = adapter->FirstUnicastAddress; unicast; unicast = unicast->Next)
if (unicast->Address.lpSockaddr && unicast->Address.lpSockaddr->sa_family == AF_INET) ++count;
for (auto* unicast = adapter->FirstUnicastAddress; unicast; unicast = unicast->Next) {
const sockaddr* address = unicast->Address.lpSockaddr;
if (!address || (address->sa_family != AF_INET && address->sa_family != AF_INET6)) continue;
HostInterfaceAddress entry = HostAddress(address);
const unsigned bits = entry.family == AF_INET ? 32 : 128;
unsigned prefix = unicast->OnLinkPrefixLength;
if (prefix > bits) prefix = bits;
for (unsigned bit = 0; bit < prefix; ++bit) entry.netmask[bit / 8] |= static_cast<std::uint8_t>(0x80 >> (bit % 8));
entries.push_back(entry);
}
#else
ifaddrs* host = nullptr;
Require(getifaddrs(&host) == 0);
for (auto* item = host; item; item = item->ifa_next)
if (item->ifa_addr && item->ifa_addr->sa_family == AF_INET) ++count;
freeifaddrs(host);
Require(::getifaddrs(&host) == 0);
try {
for (auto* item = host; item; item = item->ifa_next) {
if (!item->ifa_addr || (item->ifa_addr->sa_family != AF_INET && item->ifa_addr->sa_family != AF_INET6)) continue;
Require(item->ifa_netmask != nullptr);
HostInterfaceAddress entry = HostAddress(item->ifa_addr);
if (entry.family == AF_INET)
std::memcpy(entry.netmask, &reinterpret_cast<const sockaddr_in*>(item->ifa_netmask)->sin_addr, 4);
else
std::memcpy(entry.netmask, &reinterpret_cast<const sockaddr_in6*>(item->ifa_netmask)->sin6_addr, 16);
entries.push_back(entry);
}
} catch (...) {
::freeifaddrs(host);
throw;
}
::freeifaddrs(host);
#endif
return count;
return entries;
}
int main() {
constexpr std::uint32_t knownFlags = 0x1 | 0x2 | 0x8 | 0x10 | 0x40 | 0x80 | 0x100 | 0x200 | 0x8000;
const std::uint8_t loopbackAddress[] = {127, 0, 0, 1};
const std::uint8_t loopbackMask[] = {255, 0, 0, 0};
GuestInterfaceAddress* list = nullptr;
Require(getifaddrs_nid_postfix(&list) == 0 && list != nullptr);
std::size_t entries = 0;
std::size_t ipv4 = 0;
bool loopback = false;
for (auto* item = list; item; item = item->next) {
Require(++entries < 100000);
Require(item->name != nullptr);
const std::size_t nameLength = std::strlen(item->name);
Require(nameLength >= 1 && nameLength <= 15);
Require((item->flags & ~knownFlags) == 0);
Require(item->data == nullptr);
const auto* address = static_cast<const std::uint8_t*>(item->address);
const auto* netmask = static_cast<const std::uint8_t*>(item->netmask);
Require(address != nullptr && netmask != nullptr);
Require((address[0] == 16 && address[1] == 2) || (address[0] == 28 && address[1] == 28));
Require(netmask[0] == address[0] && netmask[1] == address[1]);
if (item->destination) {
const auto* destination = static_cast<const std::uint8_t*>(item->destination);
Require(destination[0] == address[0] && destination[1] == address[1]);
bool matched = false;
for (unsigned attempt = 0; attempt < 3 && !matched; ++attempt) {
const std::vector<HostInterfaceAddress> host = HostSnapshot();
GuestInterfaceAddress* list = nullptr;
Require(getifaddrs_nid_postfix(&list) == 0);
std::size_t entries = 0;
bool equal = true;
bool loopback = false;
for (auto* item = list; item; item = item->next) {
Require(++entries < 100000);
Require(item->name != nullptr);
const std::size_t nameLength = std::strlen(item->name);
Require(nameLength >= 1 && nameLength <= 15);
Require((item->flags & ~knownFlags) == 0);
Require(item->data == nullptr);
const auto* address = static_cast<const std::uint8_t*>(item->address);
const auto* netmask = static_cast<const std::uint8_t*>(item->netmask);
Require(address != nullptr && netmask != nullptr);
Require(reinterpret_cast<std::uintptr_t>(address) % 8 == 0);
Require(reinterpret_cast<std::uintptr_t>(netmask) % 8 == 0);
Require((address[0] == 16 && address[1] == 2) || (address[0] == 28 && address[1] == 28));
Require(netmask[0] == address[0] && netmask[1] == address[1]);
if (item->destination) {
const auto* destination = static_cast<const std::uint8_t*>(item->destination);
Require(reinterpret_cast<std::uintptr_t>(destination) % 8 == 0);
Require(destination[0] == address[0] && destination[1] == address[1]);
}
if (address[1] == 2 && (item->flags & 0x2)) Require(item->destination != nullptr);
if (item->flags & 0x10) Require(item->destination != nullptr);
if (entries <= host.size()) {
const HostInterfaceAddress& expected = host[entries - 1];
const bool ipv4 = address[1] == 2;
const std::size_t offset = ipv4 ? 4 : 8;
const std::size_t length = ipv4 ? 4 : 16;
equal = equal && expected.family == (ipv4 ? AF_INET : AF_INET6) &&
std::memcmp(address + offset, expected.address, length) == 0 &&
std::memcmp(netmask + offset, expected.netmask, length) == 0;
if (!ipv4) {
std::uint32_t scope;
std::memcpy(&scope, address + 24, 4);
equal = equal && scope == expected.scope;
}
} else {
equal = false;
}
if (address[1] == 2 && std::memcmp(address + 4, loopbackAddress, 4) == 0)
loopback = loopback || (std::memcmp(netmask + 4, loopbackMask, 4) == 0 && (item->flags & 0x9) == 0x9 && !(item->flags & 0x2));
}
if (address[1] != 2) continue;
++ipv4;
if (std::memcmp(address + 4, loopbackAddress, 4) == 0)
loopback = std::memcmp(netmask + 4, loopbackMask, 4) == 0 && (item->flags & 0x9) == 0x9 && !(item->flags & 0x2);
matched = equal && entries == host.size() && loopback;
freeifaddrs_nid_postfix(list);
}
Require(loopback);
Require(ipv4 == HostIpv4Count());
freeifaddrs_nid_postfix(list);
Require(matched);
freeifaddrs_nid_postfix(nullptr);
bool rejected = false;
try {
getifaddrs_nid_postfix(nullptr);
} catch (const std::invalid_argument&) {
rejected = true;
}
Require(rejected);
*__error_nid_postfix() = 0;
Require(getifaddrs_nid_postfix(nullptr) == -1);
Require(*__error_nid_postfix() == 14);
}

View File

@ -438,4 +438,4 @@ Throughout the project, every function at every stage either **does exactly what
- [sigaction](../../core/libs/prx/libkernel/System/src/Signals.cpp) (libkernel) records the handler, flags and mask of every signal from 1 to 128 and reports them back, following FreeBSD's `sigaction` (`EINVAL` for other signals, and for a handler on `SIGKILL` or `SIGSTOP`); `signal` records its handler with `SA_RESTART`, as FreeBSD's libc does. Only handlers without `SA_SIGINFO` on the signals `signal` already delivers (`SIGINT`, `SIGILL`, `SIGABRT`, `SIGFPE`, `SIGSEGV`, `SIGTERM`) run, the same way `signal` handlers do; their mask and flags are not applied. `SA_SIGINFO` handlers and other signals (`SIGBUS`, `SIGHUP`, ...) are recorded but never called, since that needs FreeBSD's `siginfo_t` and `ucontext_t`; `raise` of an `SA_SIGINFO` handler on a signal `signal` delivers throws instead of terminating, and `raise` of the other signals still returns `EINVAL`
- [AJM](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp), [APR](../../core/libs/prx/libkernel/Apr/src/Apr.cpp) and [NGS2](../../core/libs/prx/libSceNgs2.native/src/System.cpp) state is allocated with `new` and never freed on purpose: title threads still call into these libraries while `std::exit` runs static destructors, so ordinary statics would be destroyed under them. Keep them leaked; `guest_ajm_teardown` covers the AJM case on Unix.
- [Null vertex buffer V#](../../core/libs/prx/libSceAgcDriver/Graphics/include/VertexInput.hpp) (libSceAgcDriver) - a V# whose four dwords are all zero is bound to a zeroed vertex buffer of the attribute's size with stride 0, so every component of its attribute reads 0. This follows the V# fields in the [AMD RDNA2 ISA](https://gpuopen.com/download/rdna2-shader-instruction-set-architecture.pdf) (`NUM_RECORDS` 0, `DST_SEL` 0 selects 0), not measured on hardware.
- [getifaddrs](../../core/libs/prx/libc/src/Interfaces.cpp) (libc) lists the host's IPv4 and IPv6 addresses with FreeBSD layouts and flags. FreeBSD's per-interface AF_LINK entries and their if_data are not produced, and Windows interface names are the host's NDIS names (for example ethernet_32769), not the console's.
- [getifaddrs](../../core/libs/prx/libc/src/Interfaces.cpp) (libc) lists the host's IPv4 and IPv6 addresses in FreeBSD's `ifaddrs` and sockaddr layouts. FreeBSD's per-interface AF_LINK entries (MAC, index, MTU) and their `if_data` are not produced, so interfaces without an address are omitted. Names are the host's (Linux `lo`, `eth0`; Windows NDIS names such as `ethernet_32769`, or `if<index>` when one does not fit 15 bytes), not the console's. On Windows the flags are derived from the adapter: PPP and tunnel adapters get neither `IFF_BROADCAST` nor `IFF_POINTOPOINT` because their peer address is not reported, and an address whose prefix length the host reports as unspecified gets a host mask.