diff --git a/core/libs/prx/libc/src/Interfaces.cpp b/core/libs/prx/libc/src/Interfaces.cpp index 401f5bfd8..7e8aadef3 100644 --- a/core/libs/prx/libc/src/Interfaces.cpp +++ b/core/libs/prx/libc/src/Interfaces.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -91,7 +92,7 @@ std::size_t Aligned(std::size_t size) { #ifdef _WIN32 std::vector 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(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 HostEntries() { +int HostEntries(std::vector& entries) { const ULONG options = GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER; ULONG size = 0; std::vector 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(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 entries; for (auto* adapter = reinterpret_cast(storage.data()); adapter; adapter = adapter->Next) { std::string name; for (auto* unicast = adapter->FirstUnicastAddress; unicast; unicast = unicast->Next) { @@ -145,7 +147,7 @@ std::vector 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 HostEntries() { +int HostEntries(std::vector& entries) { ifaddrs* host = nullptr; - if (::getifaddrs(&host) != 0) throw std::runtime_error(std::string("getifaddrs: host enumeration failed: ") + std::strerror(errno)); - std::vector 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 HostEntries() { throw; } ::freeifaddrs(host); - return entries; + return 0; } #endif } @@ -186,41 +196,44 @@ std::vector HostEntries() { extern "C" { int APS5_VABI getifaddrs_nid_postfix(GuestInterfaceAddress** list) { - if (!list) throw std::invalid_argument("getifaddrs: null list"); - const std::vector entries = HostEntries(); - if (entries.empty()) { - *list = nullptr; + if (!list) { *__error_nid_postfix() = 14; return -1; } + try { + std::vector 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::malloc(size)); + if (!block) { + *__error_nid_postfix() = 12; + return -1; + } + auto* items = reinterpret_cast(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(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::malloc(size)); - if (!block) { - *__error_nid_postfix() = 12; - return -1; - } - auto* items = reinterpret_cast(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(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) { diff --git a/core/libs/tests/GuestInterfaces.cpp b/core/libs/tests/GuestInterfaces.cpp index 05635b9e6..634b1b728 100644 --- a/core/libs/tests/GuestInterfaces.cpp +++ b/core/libs/tests/GuestInterfaces.cpp @@ -12,7 +12,6 @@ #include #include #include -#include #include 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(address)->sin_addr, 4); + } else { + const auto* v6 = reinterpret_cast(address); + std::memcpy(entry.address, &v6->sin6_addr, 16); + entry.scope = v6->sin6_scope_id; + } + return entry; +} + +static std::vector HostSnapshot() { + std::vector entries; #ifdef _WIN32 const ULONG options = GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER; ULONG size = 0; std::vector 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(storage.data()), &size); } + if (result == ERROR_NO_DATA) return entries; Require(result == NO_ERROR); for (auto* adapter = reinterpret_cast(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(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(item->ifa_netmask)->sin_addr, 4); + else + std::memcpy(entry.netmask, &reinterpret_cast(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(item->address); - const auto* netmask = static_cast(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(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 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(item->address); + const auto* netmask = static_cast(item->netmask); + Require(address != nullptr && netmask != nullptr); + Require(reinterpret_cast(address) % 8 == 0); + Require(reinterpret_cast(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(item->destination); + Require(reinterpret_cast(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); } diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 19c53a157..ba94c676f 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -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` 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.