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.