Files
AnyPS5/core/libs/tests/GuestInterfaces.cpp
Federico Marras 792fb27631 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.
2026-10-19 18:57:12 +02:00

309 lines
14 KiB
C++

#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <winsock2.h>
#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"
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#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);
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);
void APS5_VABI free_nid_postfix(void* pointer);
int* APS5_VABI __error_nid_postfix();
}
static void Require(bool value) { if (!value) std::abort(); }
struct HostInterfaceAddress {
int family;
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) {
HostInterfaceAddress entry{};
entry.family = address->sa_family;
if (entry.family == AF_INET) {
std::memcpy(entry.address, &reinterpret_cast<const sockaddr_in*>(address)->sin_addr, 4);
} else {
const auto* v6 = reinterpret_cast<const sockaddr_in6*>(address);
std::memcpy(entry.address, &v6->sin6_addr, 16);
entry.scope = v6->sin6_scope_id;
}
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
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);
for (unsigned calls = 1; result == ERROR_BUFFER_OVERFLOW && calls < 4; ++calls) {
storage.resize(size);
result = GetAdaptersAddresses(AF_UNSPEC, options, nullptr, reinterpret_cast<IP_ADAPTER_ADDRESSES*>(storage.data()), &size);
}
if (result == ERROR_NO_DATA) return entries;
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) {
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<std::uint8_t>(0x80 >> (bit % 8));
entries.push_back(entry);
}
#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) 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);
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;
}
int main() {
std::array<void*, 10> api{};
ApplicationHeapRegister_nid_no_patch(api.data());
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};
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;
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);
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);
Require(reinterpret_cast<std::uintptr_t>(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]);
Require(address[2] == 0 && address[3] == 0 && netmask[2] == 0 && netmask[3] == 0);
if (address[1] == 2) {
for (std::size_t index = 8; index < 16; ++index) Require(address[index] == 0 && netmask[index] == 0);
} else {
for (std::size_t index = 4; index < 8; ++index) Require(address[index] == 0 && netmask[index] == 0);
}
if (item->destination) {
const auto* destination = static_cast<const std::uint8_t*>(item->destination);
Require(reinterpret_cast<std::uintptr_t>(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];
#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;
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));
}
matched = equal && entries == host.size() && loopback;
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);
freeifaddrs_nid_postfix(nullptr);
*__error_nid_postfix() = 0;
Require(getifaddrs_nid_postfix(nullptr) == -1);
Require(*__error_nid_postfix() == 14);
}