feat(libc): list each interface's AF_LINK entry in getifaddrs on Linux

getifaddrs listed only the host's IPv4 and IPv6 addresses, so the
per-interface AF_LINK entries FreeBSD's getifaddrs puts first were
missing, and with them the interface index, hardware address, MTU and
counters and every interface without an address.

On Linux each interface now starts with an AF_LINK entry, followed by
its addresses, as FreeBSD 11.4's getifaddrs orders them. Its address is
a sockaddr_dl with the host index, type, name and hardware address
(none for a loopback), sized as FreeBSD's if_attach sizes it. Its
ifa_data is a 152-byte FreeBSD if_data filled as if_data_copy fills it:
type, address and header length, link state, MTU, speed and the host's
packet, byte, error, drop and multicast counters from sysfs, or the
host list's 32-bit counters when sysfs cannot be read. Fields with no
host equivalent are 0 and listed in the debt entry. Windows is
unchanged.
This commit is contained in:
Federico Marras committed 2026-10-19 18:57:12 +02:00
1 parent 7a015c0946
commit 792fb27631
3 files changed
+265 -9

No files matched your search

+135 -5
View File
@@ -5,9 +5,17 @@
#include <iphlpapi.h>
#else
#include <ifaddrs.h>
#include <linux/if_link.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <netinet/in.h>
#include <netpacket/packet.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <unistd.h>
#include <algorithm>
#include <cerrno>
#include <fstream>
#endif
#include "prx/libc/include/ApplicationHeap.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
@@ -15,6 +23,7 @@
#include <cstdint>
#include <cstring>
#include <new>
#include <optional>
#include <stdexcept>
#include <string>
#include <utility>
@@ -52,6 +61,7 @@ struct Entry {
std::vector<std::uint8_t> address;
std::vector<std::uint8_t> netmask;
std::vector<std::uint8_t> destination;
std::vector<std::uint8_t> data;
};
std::vector<std::uint8_t> GuestIpv4(const std::uint8_t* bytes) {
@@ -150,6 +160,100 @@ int HostEntries(std::vector<Entry>& entries) {
return 0;
}
#else
constexpr std::uint8_t guestLinkFamily = 18;
constexpr std::size_t guestLinkNameArea = 8 + 16;
constexpr std::size_t guestLinkMinimum = 54;
constexpr std::uint8_t guestTypeOther = 0x1;
constexpr std::uint8_t guestTypeEthernet = 0x6;
constexpr std::uint8_t guestTypePpp = 0x17;
constexpr std::uint8_t guestTypeLoopback = 0x18;
constexpr std::uint8_t guestLinkStateDown = 1;
constexpr std::uint8_t guestLinkStateUp = 2;
constexpr std::size_t guestInterfaceDataSize = 152;
constexpr unsigned int hostLowerUp = 0x10000;
struct HostInterface {
std::string name;
bool linked = false;
Entry link;
std::vector<Entry> addresses;
};
template<typename TValue>
void Store(std::vector<std::uint8_t>& bytes, std::size_t offset, TValue value) {
std::memcpy(bytes.data() + offset, &value, sizeof(value));
}
std::vector<std::uint8_t> GuestLinkAddress(const std::string& name, std::uint16_t index, std::uint8_t type, const std::uint8_t* hardware, std::size_t hardwareLength) {
std::size_t size = std::max(guestLinkMinimum, guestLinkNameArea + hardwareLength);
size = Aligned(size);
if (size > 0xFF) throw std::runtime_error("getifaddrs: link-level address of " + name + " does not fit sockaddr_dl");
std::vector<std::uint8_t> guest(size, 0);
guest[0] = static_cast<std::uint8_t>(size);
guest[1] = guestLinkFamily;
Store(guest, 2, index);
guest[4] = type;
guest[5] = static_cast<std::uint8_t>(name.size());
guest[6] = static_cast<std::uint8_t>(hardwareLength);
std::memcpy(guest.data() + 8, name.data(), name.size());
if (hardwareLength != 0) std::memcpy(guest.data() + 8 + name.size(), hardware, hardwareLength);
return guest;
}
std::optional<std::uint64_t> ReadSysfsCounter(const std::string& name, const char* file) {
std::ifstream input("/sys/class/net/" + name + "/" + file);
long long value = 0;
if (!(input >> value)) return std::nullopt;
return static_cast<std::uint64_t>(value);
}
std::uint32_t HostMtu(const std::string& name) {
const int probe = ::socket(AF_INET, SOCK_DGRAM, 0);
if (probe < 0) throw std::runtime_error(std::string("getifaddrs: no socket to read the MTU of ") + name + ": " + std::strerror(errno));
ifreq request{};
std::memcpy(request.ifr_name, name.c_str(), name.size() + 1);
const int result = ::ioctl(probe, SIOCGIFMTU, &request);
const int error = errno;
::close(probe);
if (result != 0) throw std::runtime_error(std::string("getifaddrs: SIOCGIFMTU failed on ") + name + ": " + std::strerror(error));
return static_cast<std::uint32_t>(request.ifr_mtu);
}
std::vector<std::uint8_t> GuestInterfaceData(const std::string& name, unsigned int hostFlags, const void* hostStats, std::uint8_t type, std::uint8_t hardwareLength) {
std::vector<std::uint8_t> data(guestInterfaceDataSize, 0);
data[0] = type;
data[2] = hardwareLength;
data[3] = type == guestTypeEthernet ? 14 : 0;
if (type == guestTypeLoopback) data[4] = (hostFlags & IFF_UP) ? guestLinkStateUp : guestLinkStateDown;
else data[4] = (hostFlags & hostLowerUp) ? guestLinkStateUp : guestLinkStateDown;
Store(data, 6, static_cast<std::uint16_t>(guestInterfaceDataSize));
Store(data, 8, HostMtu(name));
if (const auto speed = ReadSysfsCounter(name, "speed"); speed && static_cast<std::int64_t>(*speed) > 0)
Store(data, 16, *speed * 1000000u);
constexpr std::pair<const char*, std::size_t> counters[] = {
{"statistics/rx_packets", 24}, {"statistics/rx_errors", 32}, {"statistics/tx_packets", 40}, {"statistics/tx_errors", 48},
{"statistics/collisions", 56}, {"statistics/rx_bytes", 64}, {"statistics/tx_bytes", 72}, {"statistics/multicast", 80},
{"statistics/rx_dropped", 96}, {"statistics/tx_dropped", 104},
};
bool complete = true;
for (const auto& [file, offset] : counters) {
const auto value = ReadSysfsCounter(name, file);
if (!value) { complete = false; break; }
Store(data, offset, *value);
}
if (!complete) {
if (!hostStats) throw std::runtime_error("getifaddrs: no counters for " + name + " in sysfs or the host list");
rtnl_link_stats stats{};
std::memcpy(&stats, hostStats, sizeof(stats));
const std::pair<std::uint32_t, std::size_t> fallback[] = {
{stats.rx_packets, 24}, {stats.rx_errors, 32}, {stats.tx_packets, 40}, {stats.tx_errors, 48}, {stats.collisions, 56},
{stats.rx_bytes, 64}, {stats.tx_bytes, 72}, {stats.multicast, 80}, {stats.rx_dropped, 96}, {stats.tx_dropped, 104},
};
for (const auto& [value, offset] : fallback) Store(data, offset, static_cast<std::uint64_t>(value));
}
return data;
}
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},
@@ -174,9 +278,31 @@ int HostEntries(std::vector<Entry>& entries) {
default: throw std::runtime_error(std::string("getifaddrs: host enumeration failed: ") + std::strerror(hostError));
}
}
std::vector<HostInterface> interfaces;
const auto interfaceFor = [&](const std::string& name) -> HostInterface& {
const auto found = std::find_if(interfaces.begin(), interfaces.end(), [&](const HostInterface& candidate) { return candidate.name == name; });
if (found != interfaces.end()) return *found;
interfaces.push_back({name, false, {}, {}});
return interfaces.back();
};
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_addr) continue;
if (item->ifa_addr->sa_family == AF_PACKET) {
const auto& link = *reinterpret_cast<const sockaddr_ll*>(item->ifa_addr);
HostInterface& owner = interfaceFor(CheckedName(item->ifa_name));
if (owner.linked) throw std::runtime_error("getifaddrs: two link-level entries for " + owner.name);
if (link.sll_ifindex <= 0 || link.sll_ifindex > 0xFFFF) throw std::runtime_error("getifaddrs: interface index of " + owner.name + " does not fit sockaddr_dl");
const std::uint8_t type = link.sll_hatype == ARPHRD_ETHER ? guestTypeEthernet : link.sll_hatype == ARPHRD_LOOPBACK ? guestTypeLoopback
: link.sll_hatype == ARPHRD_PPP ? guestTypePpp : guestTypeOther;
const std::uint8_t hardwareLength = type == guestTypeLoopback ? 0 : std::min<std::uint8_t>(link.sll_halen, sizeof(link.sll_addr));
owner.link = Entry{owner.name, InterfaceFlags(item->ifa_flags),
GuestLinkAddress(owner.name, static_cast<std::uint16_t>(link.sll_ifindex), type, link.sll_addr, hardwareLength), {}, {},
GuestInterfaceData(owner.name, item->ifa_flags, item->ifa_data, type, hardwareLength)};
owner.linked = true;
continue;
}
if (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 int family = item->ifa_addr->sa_family;
@@ -187,13 +313,17 @@ int HostEntries(std::vector<Entry>& entries) {
entry.destination = item->ifa_broadaddr && item->ifa_broadaddr->sa_family == AF_INET
? GuestAddress(item->ifa_broadaddr) : BroadcastAddress(entry.address, entry.netmask);
}
entries.push_back(std::move(entry));
interfaceFor(entry.name).addresses.push_back(std::move(entry));
}
} catch (...) {
::freeifaddrs(host);
throw;
}
::freeifaddrs(host);
for (auto& owner : interfaces) {
if (owner.linked) entries.push_back(std::move(owner.link));
for (auto& address : owner.addresses) entries.push_back(std::move(address));
}
return 0;
}
#endif
@@ -212,7 +342,7 @@ int APS5_VABI getifaddrs_nid_postfix(GuestInterfaceAddress** list) {
}
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());
size += Aligned(entry.name.size() + 1) + Aligned(entry.address.size()) + Aligned(entry.netmask.size()) + Aligned(entry.destination.size()) + Aligned(entry.data.size());
auto* block = static_cast<std::uint8_t*>(ApplicationHeapAllocate_nid_no_patch(size));
if (!block) {
*__error_nid_postfix() = 12;
@@ -233,9 +363,9 @@ int APS5_VABI getifaddrs_nid_postfix(GuestInterfaceAddress** list) {
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.netmask = entry.netmask.empty() ? nullptr : place(entry.netmask.data(), entry.netmask.size());
item.destination = entry.destination.empty() ? nullptr : place(entry.destination.data(), entry.destination.size());
item.data = nullptr;
item.data = entry.data.empty() ? nullptr : place(entry.data.data(), entry.data.size());
}
*list = items;
return 0;
+129 -3
View File
@@ -4,8 +4,17 @@
#include <ws2tcpip.h>
#include <iphlpapi.h>
#else
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <netinet/in.h>
#include <netpacket/packet.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <unistd.h>
#include <fstream>
#include <string>
#endif
#include "prx/libc/include/ApplicationHeap.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
@@ -46,6 +55,12 @@ struct HostInterfaceAddress {
std::uint8_t address[16];
std::uint8_t netmask[16];
std::uint32_t scope;
char name[16];
unsigned int index;
std::uint8_t type;
std::uint8_t hardwareLength;
std::uint8_t hardware[8];
bool loopbackUp;
};
static HostInterfaceAddress HostAddress(const sockaddr* address) {
@@ -61,6 +76,43 @@ static HostInterfaceAddress HostAddress(const sockaddr* address) {
return entry;
}
#ifndef _WIN32
static std::uint64_t SysfsCounter(const char* name, const char* file) {
std::ifstream input(std::string("/sys/class/net/") + name + "/statistics/" + file);
unsigned long long value = 0;
Require(static_cast<bool>(input >> value));
return value;
}
static std::uint32_t HostMtu(const char* name) {
const int probe = ::socket(AF_INET, SOCK_DGRAM, 0);
Require(probe >= 0);
ifreq request{};
std::strncpy(request.ifr_name, name, sizeof(request.ifr_name) - 1);
Require(::ioctl(probe, SIOCGIFMTU, &request) == 0);
::close(probe);
return static_cast<std::uint32_t>(request.ifr_mtu);
}
static void SendLoopbackDatagram() {
const int receiver = ::socket(AF_INET, SOCK_DGRAM, 0);
const int sender = ::socket(AF_INET, SOCK_DGRAM, 0);
Require(receiver >= 0 && sender >= 0);
sockaddr_in address{};
address.sin_family = AF_INET;
address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
Require(::bind(receiver, reinterpret_cast<const sockaddr*>(&address), sizeof(address)) == 0);
socklen_t length = sizeof(address);
Require(::getsockname(receiver, reinterpret_cast<sockaddr*>(&address), &length) == 0);
const char payload = 'x';
Require(::sendto(sender, &payload, 1, 0, reinterpret_cast<const sockaddr*>(&address), sizeof(address)) == 1);
char received = 0;
Require(::recv(receiver, &received, 1, 0) == 1);
::close(sender);
::close(receiver);
}
#endif
static std::vector<HostInterfaceAddress> HostSnapshot() {
std::vector<HostInterfaceAddress> entries;
#ifdef _WIN32
@@ -88,22 +140,47 @@ static std::vector<HostInterfaceAddress> HostSnapshot() {
#else
ifaddrs* host = nullptr;
Require(::getifaddrs(&host) == 0);
std::vector<std::string> names;
std::vector<std::vector<HostInterfaceAddress>> groups;
const auto group = [&](const char* name) -> std::vector<HostInterfaceAddress>& {
for (std::size_t index = 0; index < names.size(); ++index)
if (names[index] == name) return groups[index];
names.emplace_back(name);
return groups.emplace_back();
};
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_addr) continue;
if (item->ifa_addr->sa_family == AF_PACKET) {
const auto& link = *reinterpret_cast<const sockaddr_ll*>(item->ifa_addr);
HostInterfaceAddress entry{};
entry.family = AF_PACKET;
std::strncpy(entry.name, item->ifa_name, sizeof(entry.name) - 1);
entry.index = static_cast<unsigned int>(link.sll_ifindex);
entry.type = link.sll_hatype == ARPHRD_ETHER ? 0x6 : link.sll_hatype == ARPHRD_LOOPBACK ? 0x18 : link.sll_hatype == ARPHRD_PPP ? 0x17 : 0x1;
entry.hardwareLength = entry.type == 0x18 ? 0 : link.sll_halen;
std::memcpy(entry.hardware, link.sll_addr, entry.hardwareLength);
entry.loopbackUp = (item->ifa_flags & IFF_UP) != 0;
auto& members = group(item->ifa_name);
members.insert(members.begin(), entry);
continue;
}
if (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);
std::strncpy(entry.name, item->ifa_name, sizeof(entry.name) - 1);
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);
group(item->ifa_name).push_back(entry);
}
} catch (...) {
::freeifaddrs(host);
throw;
}
::freeifaddrs(host);
for (const auto& members : groups) entries.insert(entries.end(), members.begin(), members.end());
#endif
return entries;
}
@@ -115,6 +192,10 @@ int main() {
const std::uint8_t loopbackAddress[] = {127, 0, 0, 1};
const std::uint8_t loopbackMask[] = {255, 0, 0, 0};
bool matched = false;
#ifndef _WIN32
bool linkChecked = false;
SendLoopbackDatagram();
#endif
for (unsigned attempt = 0; attempt < 3 && !matched; ++attempt) {
const std::vector<HostInterfaceAddress> host = HostSnapshot();
GuestInterfaceAddress* list = nullptr;
@@ -128,8 +209,47 @@ int main() {
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);
Require(address != nullptr && reinterpret_cast<std::uintptr_t>(address) % 8 == 0);
#ifndef _WIN32
if (address[1] == 18) {
Require(item->netmask == nullptr && item->destination == nullptr);
const auto* data = static_cast<const std::uint8_t*>(item->data);
Require(data != nullptr && reinterpret_cast<std::uintptr_t>(data) % 8 == 0);
std::uint16_t index = 0;
std::uint16_t dataLength = 0;
std::uint32_t mtu = 0;
std::uint64_t counters[11] = {};
std::memcpy(&index, address + 2, 2);
std::memcpy(&dataLength, data + 6, 2);
std::memcpy(&mtu, data + 8, 4);
std::memcpy(counters, data + 24, sizeof(counters));
Require(address[0] >= 54 && address[0] % 8 == 0 && address[0] >= 24 + address[6]);
Require(address[5] == nameLength && std::memcmp(address + 8, item->name, nameLength) == 0 && address[7] == 0);
Require(dataLength == 152 && data[0] == address[4] && data[2] == address[6] && data[1] == 0 && data[5] == 0);
Require(data[4] == 1 || data[4] == 2);
Require(index == if_nametoindex(item->name));
Require(mtu == HostMtu(item->name));
Require(counters[0] <= SysfsCounter(item->name, "rx_packets") && counters[2] <= SysfsCounter(item->name, "tx_packets"));
Require(counters[5] <= SysfsCounter(item->name, "rx_bytes") && counters[6] <= SysfsCounter(item->name, "tx_bytes"));
if (entries <= host.size()) {
const HostInterfaceAddress& expected = host[entries - 1];
equal = equal && expected.family == AF_PACKET && std::strcmp(expected.name, item->name) == 0 && expected.index == index &&
expected.type == address[4] && expected.hardwareLength == address[6] &&
std::memcmp(expected.hardware, address + 8 + nameLength, expected.hardwareLength) == 0;
if (expected.type == 0x18) {
Require(data[3] == 0 && data[4] == (expected.loopbackUp ? 2 : 1));
if (expected.loopbackUp) Require(counters[0] > 0 && counters[2] > 0 && counters[5] > 0 && counters[6] > 0);
linkChecked = linkChecked || expected.loopbackUp;
}
if (expected.type == 0x6) Require(data[3] == 14 && address[6] == 6);
} else {
equal = false;
}
continue;
}
#endif
Require(item->data == nullptr);
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);
@@ -151,6 +271,9 @@ int main() {
if (item->flags & 0x10) Require(item->destination != nullptr);
if (entries <= host.size()) {
const HostInterfaceAddress& expected = host[entries - 1];
#ifndef _WIN32
equal = equal && std::strcmp(expected.name, item->name) == 0;
#endif
const bool ipv4 = address[1] == 2;
const std::size_t offset = ipv4 ? 4 : 8;
const std::size_t length = ipv4 ? 4 : 16;
@@ -172,6 +295,9 @@ int main() {
freeifaddrs_nid_postfix(list);
}
Require(matched);
#ifndef _WIN32
Require(linkChecked);
#endif
GuestInterfaceAddress* released = nullptr;
Require(getifaddrs_nid_postfix(&released) == 0 && released != nullptr);
free_nid_postfix(released);
+1 -1
View File
@@ -89,7 +89,7 @@
- The [`_Unwind_Context`](../../../core/libs/prx/libc/include/exceptions/Unwind.hpp) that `_Unwind_Backtrace` passes to its callback starts with a table laid out like LLVM libunwind's `AbstractUnwindCursor` virtual table, because homebrew built with the ps5-payload-dev SDK links libunwind statically and reads the frames with its own `_Unwind_GetIP` (`__unw_get_reg`), which calls through that table. Only register reads and writes (`validReg`, `getReg`, `setReg`, with `UNW_REG_IP` and `UNW_REG_SP`), `validFloatReg` (always false) and `isSignalFrame` are answered; the other slots throw. The layout follows libunwind's `UnwindCursor.hpp`, not the console's own `libSceLibcInternal`, which is not checked
- The Windows libc [exception unwinder](../../../core/libs/prx/libc/src/exception/Unwind.cpp) steps through frames of modules without the project's `.ehmeta` and `.ehfram` tables (system DLLs, and WinLibs GCC 15.2's `libstdc++-6.dll` and `libwinpthread-1.dll`, whose `.eh_frame` sections are empty) with their PE unwind data through `RtlVirtualUnwind`, but does not run Windows exception or termination handlers: it stops at a frame whose unwind data has one, such as a C++ cleanup through `__gxx_personality_seh0` in `libstdc++-6.dll` or a system DLL function like `qsort` in `ucrtbase.dll` (Windows 10 22H2), and the exception is then reported as unhandled
- The libc [SSE4a trap emulation](../../../core/libs/prx/libc/src/specifics/windows/Sse4aEmulation.hpp) on Windows is superseded by `--to-intel` and remains only until the relinked title has been verified without it.
- [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. A Linux point-to-point address without a peer loses `IFF_POINTOPOINT`, and an IPv4 broadcast address the host omits is computed from the netmask. On Windows the flags are only `IFF_UP`, `IFF_RUNNING`, `IFF_LOOPBACK`, `IFF_BROADCAST` and `IFF_MULTICAST`, 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.
- [getifaddrs](../../../core/libs/prx/libc/src/Interfaces.cpp) (libc) lists host interfaces and IPv4/IPv6 addresses in FreeBSD's layouts. Windows omits per-interface AF_LINK entries and interfaces without an address; its flags are only `IFF_UP`, `IFF_RUNNING`, `IFF_LOOPBACK`, `IFF_BROADCAST` and `IFF_MULTICAST` (PPP/tunnel adapters omit broadcast and point-to-point, and an unspecified prefix length gets a host mask). On Linux, `AF_LINK` `if_data` maps non-Ethernet/loopback/PPP types to `IFT_OTHER` (1) and leaves `ifi_omcasts`, `ifi_noproto`, `ifi_hwassist`, `ifi_metric`, `ifi_epoch`, `ifi_lastchange`, unknown baud rates and address-entry `ifa_data` as 0/null; a point-to-point address without a peer loses `IFF_POINTOPOINT`, and an omitted IPv4 broadcast is derived from the netmask. Interface names are the host's (`if<index>` on Windows above 15 bytes).
- [gethostname](../../../core/libs/prx/libc/src/Hostname.cpp) (libc) reports the fixed guest name `AnyPS5` without querying the host machine name. Buffer copying, null queries and `ENAMETOOLONG` follow [FreeBSD gethostname](https://github.com/freebsd/freebsd-src/blob/main/lib/libc/gen/gethostname.c); the console's configurable hostname and its integration with `sysctl` and name resolution are unverified and not implemented.
- [wcsrtombs_s](../../../core/libs/prx/libc/src/Multibyte.cpp) (libc) follows the console's `libc.prx`, but a runtime-constraint violation only returns its error: the default handler there writes the message and a newline to `stderr` before returning, which is not done here. Conversion uses the C locale of `wcrtomb` (`MB_CUR_MAX` 1), so the path where a multibyte character does not fit the remaining space is not reached
- [c16rtomb, c32rtomb, mbrtoc16 and mbrtoc32](../../../core/libs/prx/libc/src/Multibyte.cpp) (libSceLibcInternal, implemented in libc) follow the console's `libSceLibcInternal.sprx` (SDK 4.5x build of `J03950001`, 0x7D480 to 0x7D920), which converts to and from UTF-8 whatever the locale, with the original sequences of up to six bytes and no check for surrogates or for values above 0x10FFFF when encoding. The conversion state keeps the value in its first eight bytes and the count of bytes still expected (or 0x10 for a pending low surrogate, 1 for a stored high surrogate) as 16 bits at offset 0xA; a null state, which the console dereferences, throws. As on the console, `mbrtoc16` does not store the complete code point when it returns a high surrogate, so the low surrogate of the next call is built from the value the state last held, which is the partial value of an earlier incomplete call or whatever the caller initialised it to, `c16rtomb` returns -1 without setting `errno` when a high surrogate is not followed by a low one, and `mbrtoc16` and `mbrtoc32` report `EILSEQ` as 86. Measured on a PS5 (firmware 13.60) from a homebrew payload, the system `libSceLibcInternal`'s default locale also converts UTF-8 in `wcrtomb`, `wctomb`, `mbrtowc` and `mblen` (`wctomb` of U+263A gives `E2 98 BA`, `mblen` of a lone 0xE9 gives -1); this libc keeps its single-byte C locale, so [wctomb and mblen](../../../core/libs/prx/libc/src/Multibyte.cpp), which call `wcrtomb` and `mbtowc` here as they call the same internal routines on the console, share that difference. Titles bundle their own `libc.prx`, whose locale was not measured