From 7cd185ec79092ae7f0d52b6271e6f2fa5328731d Mon Sep 17 00:00:00 2001 From: LuisBiceps <54749472+LuisBiceps@users.noreply.github.com> Date: Fri, 9 Oct 2026 02:47:53 +0800 Subject: [PATCH] feat(libc): implement getifaddrs and freeifaddrs List the host's IPv4 and IPv6 addresses in FreeBSD's struct ifaddrs layout, with FreeBSD sockaddrs and interface flags, from getifaddrs on Linux and GetAdaptersAddresses on Windows. The list is one allocation, so freeifaddrs is a single free. --- core/libs/prx/libc/CMakeLists.txt | 13 +- core/libs/prx/libc/src/Interfaces.cpp | 230 ++++++++++++++++++++++++++ core/libs/tests/GuestInterfaces.cpp | 102 ++++++++++++ docs/dev/TechnicalDebt.md | 1 + 4 files changed, 345 insertions(+), 1 deletion(-) create mode 100644 core/libs/prx/libc/src/Interfaces.cpp create mode 100644 core/libs/tests/GuestInterfaces.cpp diff --git a/core/libs/prx/libc/CMakeLists.txt b/core/libs/prx/libc/CMakeLists.txt index 5dc9ab41a..505d6d46a 100644 --- a/core/libs/prx/libc/CMakeLists.txt +++ b/core/libs/prx/libc/CMakeLists.txt @@ -19,6 +19,7 @@ target_sources(libc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/GuestAllocations.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/GuestArena.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/GuestWriteWatch.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Interfaces.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/GuestHeap.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/HeapDiagnostics.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Mspace.cpp @@ -45,7 +46,7 @@ target_include_directories(libc PRIVATE ) if(WIN32) - target_link_libraries(libc PRIVATE advapi32) + target_link_libraries(libc PRIVATE advapi32 ws2_32 iphlpapi) endif() if(BUILD_TESTING) @@ -255,6 +256,16 @@ if(BUILD_TESTING) set_tests_properties(guest_syslog PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_interface_tests ${LIBS_INCLUDE_DIR}/tests/GuestInterfaces.cpp) + target_include_directories(guest_interface_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_interface_tests PRIVATE libc) + configure_windows_unwind(guest_interface_tests) + add_test(NAME guest_interfaces COMMAND guest_interface_tests) + if(WIN32) + target_link_libraries(guest_interface_tests PRIVATE ws2_32 iphlpapi) + set_tests_properties(guest_interfaces PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(mspace_tests ${LIBS_INCLUDE_DIR}/tests/Mspace.cpp) target_include_directories(mspace_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(mspace_tests PRIVATE libc) diff --git a/core/libs/prx/libc/src/Interfaces.cpp b/core/libs/prx/libc/src/Interfaces.cpp new file mode 100644 index 000000000..401f5bfd8 --- /dev/null +++ b/core/libs/prx/libc/src/Interfaces.cpp @@ -0,0 +1,230 @@ +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#else +#include +#include +#include +#include +#endif +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" int* APS5_VABI __error_nid_postfix(); + +struct GuestInterfaceAddress { + GuestInterfaceAddress* next; + char* name; + std::uint32_t flags; + void* address; + void* netmask; + void* destination; + void* data; +}; +static_assert(sizeof(GuestInterfaceAddress) == 56); +static_assert(offsetof(GuestInterfaceAddress, flags) == 16); +static_assert(offsetof(GuestInterfaceAddress, address) == 24); +static_assert(offsetof(GuestInterfaceAddress, destination) == 40); +static_assert(offsetof(GuestInterfaceAddress, data) == 48); + +namespace { +constexpr std::uint32_t guestUp = 0x1; +constexpr std::uint32_t guestBroadcast = 0x2; +constexpr std::uint32_t guestLoopback = 0x8; +constexpr std::uint32_t guestPointToPoint = 0x10; +constexpr std::uint32_t guestRunning = 0x40; +constexpr std::uint32_t guestMulticast = 0x8000; +constexpr std::size_t guestNameCapacity = 16; + +struct Entry { + std::string name; + std::uint32_t flags; + std::vector address; + std::vector netmask; + std::vector destination; +}; + +std::vector GuestIpv4(const std::uint8_t* bytes) { + std::vector guest(16, 0); + guest[0] = 16; + guest[1] = 2; + std::memcpy(guest.data() + 4, bytes, 4); + return guest; +} + +std::vector GuestIpv6(const std::uint8_t* bytes, std::uint32_t scope) { + std::vector guest(28, 0); + guest[0] = 28; + guest[1] = 28; + std::memcpy(guest.data() + 8, bytes, 16); + std::memcpy(guest.data() + 24, &scope, 4); + return guest; +} + +std::vector GuestAddress(const sockaddr* address) { + if (address->sa_family == AF_INET) + return GuestIpv4(reinterpret_cast(&reinterpret_cast(address)->sin_addr)); + const auto* v6 = reinterpret_cast(address); + return GuestIpv6(reinterpret_cast(&v6->sin6_addr), v6->sin6_scope_id); +} + +std::string CheckedName(const char* name) { + const std::string checked(name); + if (checked.empty() || checked.size() >= guestNameCapacity) + throw std::runtime_error("getifaddrs: host interface name does not fit the guest's 16-byte limit: " + checked); + return checked; +} + +std::size_t Aligned(std::size_t size) { + return (size + 7) & ~static_cast(7); +} + +#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"); + 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); +} + +std::vector BroadcastAddress(const std::vector& address, const std::vector& netmask) { + std::vector broadcast = address; + for (std::size_t index = 4; index < 8; ++index) + broadcast[index] = static_cast(address[index] | static_cast(~netmask[index])); + return broadcast; +} + +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; + if (!(adapter->Flags & IP_ADAPTER_NO_MULTICAST)) flags |= guestMulticast; + return flags; +} + +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); +} + +std::vector HostEntries() { + 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) { + storage.resize(size); + result = GetAdaptersAddresses(AF_UNSPEC, options, nullptr, reinterpret_cast(storage.data()), &size); + } + if (result == ERROR_NO_DATA) return {}; + 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) { + const sockaddr* address = unicast->Address.lpSockaddr; + if (!address || (address->sa_family != AF_INET && address->sa_family != AF_INET6)) continue; + if (name.empty()) name = InterfaceName(adapter); + Entry entry{name, InterfaceFlags(adapter), GuestAddress(address), PrefixMask(address->sa_family, unicast->OnLinkPrefixLength), {}}; + if (address->sa_family == AF_INET && (entry.flags & guestBroadcast)) + entry.destination = BroadcastAddress(entry.address, entry.netmask); + entries.push_back(std::move(entry)); + } + } + return entries; +} +#else +std::uint32_t InterfaceFlags(unsigned int host) { + constexpr std::pair flags[] = { + {IFF_UP, guestUp}, {IFF_BROADCAST, guestBroadcast}, {IFF_LOOPBACK, guestLoopback}, + {IFF_POINTOPOINT, guestPointToPoint}, {IFF_RUNNING, guestRunning}, {IFF_NOARP, 0x80}, + {IFF_PROMISC, 0x100}, {IFF_ALLMULTI, 0x200}, {IFF_MULTICAST, guestMulticast}, + }; + std::uint32_t guest = 0; + for (const auto& [hostFlag, guestFlag] : flags) + if (host & hostFlag) guest |= guestFlag; + return guest; +} + +std::vector HostEntries() { + ifaddrs* host = nullptr; + if (::getifaddrs(&host) != 0) throw std::runtime_error(std::string("getifaddrs: host enumeration failed: ") + std::strerror(errno)); + std::vector entries; + 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; + if (!item->ifa_netmask) throw std::runtime_error(std::string("getifaddrs: host address without a netmask on ") + item->ifa_name); + Entry entry{CheckedName(item->ifa_name), InterfaceFlags(item->ifa_flags), GuestAddress(item->ifa_addr), GuestAddress(item->ifa_netmask), {}}; + const sockaddr* destination = item->ifa_flags & IFF_POINTOPOINT ? item->ifa_dstaddr : item->ifa_flags & IFF_BROADCAST ? item->ifa_broadaddr : nullptr; + if (destination && destination->sa_family == item->ifa_addr->sa_family) entry.destination = GuestAddress(destination); + entries.push_back(std::move(entry)); + } + } catch (...) { + ::freeifaddrs(host); + throw; + } + ::freeifaddrs(host); + return entries; +} +#endif +} + +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; + 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; +} + +void APS5_VABI freeifaddrs_nid_postfix(GuestInterfaceAddress* list) { + std::free(list); +} + +} diff --git a/core/libs/tests/GuestInterfaces.cpp b/core/libs/tests/GuestInterfaces.cpp new file mode 100644 index 000000000..05635b9e6 --- /dev/null +++ b/core/libs/tests/GuestInterfaces.cpp @@ -0,0 +1,102 @@ +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#else +#include +#include +#endif +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include + +struct GuestInterfaceAddress { + GuestInterfaceAddress* next; + char* name; + std::uint32_t flags; + void* address; + void* netmask; + void* destination; + void* data; +}; +static_assert(sizeof(GuestInterfaceAddress) == 56); + +extern "C" { +int APS5_VABI getifaddrs_nid_postfix(GuestInterfaceAddress** list); +void APS5_VABI freeifaddrs_nid_postfix(GuestInterfaceAddress* list); +} + +static void Require(bool value) { if (!value) std::abort(); } + +static std::size_t HostIpv4Count() { + std::size_t count = 0; +#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) { + storage.resize(size); + result = GetAdaptersAddresses(AF_UNSPEC, options, nullptr, reinterpret_cast(storage.data()), &size); + } + 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; +#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); +#endif + return count; +} + +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]); + } + 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); + } + Require(loopback); + Require(ipv4 == HostIpv4Count()); + freeifaddrs_nid_postfix(list); + freeifaddrs_nid_postfix(nullptr); + bool rejected = false; + try { + getifaddrs_nid_postfix(nullptr); + } catch (const std::invalid_argument&) { + rejected = true; + } + Require(rejected); +} diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 9850a1576..19c53a157 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -438,3 +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.