mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-22 16:43:05 -05:00
Drop IFF_POINTOPOINT from an address without a peer and compute an IPv4 broadcast address the host omits from the netmask, since FreeBSD always provides the address when the flag is set. Check zero port, flowinfo and padding in the test.
250 lines
10 KiB
C++
250 lines
10 KiB
C++
#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 <new>
|
|
#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);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
std::vector<std::uint8_t> PrefixMask(int family, unsigned prefix) {
|
|
const unsigned bits = family == AF_INET ? 32 : 128;
|
|
if (prefix > bits) prefix = bits;
|
|
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::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 |= 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));
|
|
if (std::strlen(name) < guestNameCapacity) return CheckedName(name);
|
|
return "if" + std::to_string(adapter->IfIndex ? adapter->IfIndex : adapter->Ipv6IfIndex);
|
|
}
|
|
|
|
int HostEntries(std::vector<Entry>& entries) {
|
|
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 0;
|
|
if (result == ERROR_BUFFER_OVERFLOW) { *__error_nid_postfix() = 55; return -1; }
|
|
if (result == ERROR_NOT_ENOUGH_MEMORY) { *__error_nid_postfix() = 12; return -1; }
|
|
if (result != NO_ERROR) throw std::runtime_error("getifaddrs: GetAdaptersAddresses failed with " + std::to_string(result));
|
|
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 0;
|
|
}
|
|
#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;
|
|
}
|
|
|
|
int HostEntries(std::vector<Entry>& entries) {
|
|
ifaddrs* host = nullptr;
|
|
if (::getifaddrs(&host) != 0) {
|
|
const int hostError = errno;
|
|
switch (hostError) {
|
|
case ENOMEM: *__error_nid_postfix() = 12; return -1;
|
|
case ENOBUFS: *__error_nid_postfix() = 55; return -1;
|
|
case EMFILE: *__error_nid_postfix() = 24; return -1;
|
|
case ENFILE: *__error_nid_postfix() = 23; return -1;
|
|
default: throw std::runtime_error(std::string("getifaddrs: host enumeration failed: ") + std::strerror(hostError));
|
|
}
|
|
}
|
|
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 int family = item->ifa_addr->sa_family;
|
|
if (item->ifa_flags & IFF_POINTOPOINT) {
|
|
if (item->ifa_dstaddr && item->ifa_dstaddr->sa_family == family) entry.destination = GuestAddress(item->ifa_dstaddr);
|
|
else entry.flags &= ~guestPointToPoint;
|
|
} else if (family == AF_INET && (item->ifa_flags & IFF_BROADCAST)) {
|
|
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));
|
|
}
|
|
} catch (...) {
|
|
::freeifaddrs(host);
|
|
throw;
|
|
}
|
|
::freeifaddrs(host);
|
|
return 0;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
extern "C" {
|
|
|
|
int APS5_VABI getifaddrs_nid_postfix(GuestInterfaceAddress** list) {
|
|
if (!list) { *__error_nid_postfix() = 14; return -1; }
|
|
try {
|
|
std::vector<Entry> entries;
|
|
if (HostEntries(entries) != 0) return -1;
|
|
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;
|
|
} catch (const std::bad_alloc&) { *__error_nid_postfix() = 12; return -1; }
|
|
}
|
|
|
|
void APS5_VABI freeifaddrs_nid_postfix(GuestInterfaceAddress* list) {
|
|
std::free(list);
|
|
}
|
|
|
|
}
|