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.
This commit is contained in:
parent
85d16bcc23
commit
7cd185ec79
|
|
@ -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:$<TARGET_FILE_DIR:libc>")
|
||||
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:$<TARGET_FILE_DIR:libc>")
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,230 @@
|
|||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include <iphlpapi.h>
|
||||
#else
|
||||
#include <ifaddrs.h>
|
||||
#include <net/if.h>
|
||||
#include <netinet/in.h>
|
||||
#include <cerrno>
|
||||
#endif
|
||||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
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<std::uint8_t> address;
|
||||
std::vector<std::uint8_t> netmask;
|
||||
std::vector<std::uint8_t> destination;
|
||||
};
|
||||
|
||||
std::vector<std::uint8_t> GuestIpv4(const std::uint8_t* bytes) {
|
||||
std::vector<std::uint8_t> guest(16, 0);
|
||||
guest[0] = 16;
|
||||
guest[1] = 2;
|
||||
std::memcpy(guest.data() + 4, bytes, 4);
|
||||
return guest;
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> GuestIpv6(const std::uint8_t* bytes, std::uint32_t scope) {
|
||||
std::vector<std::uint8_t> 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<std::uint8_t> GuestAddress(const sockaddr* address) {
|
||||
if (address->sa_family == AF_INET)
|
||||
return GuestIpv4(reinterpret_cast<const std::uint8_t*>(&reinterpret_cast<const sockaddr_in*>(address)->sin_addr));
|
||||
const auto* v6 = reinterpret_cast<const sockaddr_in6*>(address);
|
||||
return GuestIpv6(reinterpret_cast<const std::uint8_t*>(&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<std::size_t>(7);
|
||||
}
|
||||
|
||||
#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");
|
||||
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);
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> BroadcastAddress(const std::vector<std::uint8_t>& address, const std::vector<std::uint8_t>& netmask) {
|
||||
std::vector<std::uint8_t> broadcast = address;
|
||||
for (std::size_t index = 4; index < 8; ++index)
|
||||
broadcast[index] = static_cast<std::uint8_t>(address[index] | static_cast<std::uint8_t>(~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<Entry> HostEntries() {
|
||||
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) {
|
||||
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 != 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) {
|
||||
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<unsigned int, std::uint32_t> 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<Entry> HostEntries() {
|
||||
ifaddrs* host = nullptr;
|
||||
if (::getifaddrs(&host) != 0) throw std::runtime_error(std::string("getifaddrs: host enumeration failed: ") + std::strerror(errno));
|
||||
std::vector<Entry> 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<Entry> 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::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;
|
||||
}
|
||||
|
||||
void APS5_VABI freeifaddrs_nid_postfix(GuestInterfaceAddress* list) {
|
||||
std::free(list);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include <iphlpapi.h>
|
||||
#else
|
||||
#include <ifaddrs.h>
|
||||
#include <netinet/in.h>
|
||||
#endif
|
||||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
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<std::uint8_t> 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<IP_ADAPTER_ADDRESSES*>(storage.data()), &size);
|
||||
}
|
||||
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;
|
||||
#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<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]);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Reference in New Issue