diff --git a/core/libs/prx/libSceNet/Export.cpp b/core/libs/prx/libSceNet/Export.cpp index 6feaf0d5248..9819703ac27 100644 --- a/core/libs/prx/libSceNet/Export.cpp +++ b/core/libs/prx/libSceNet/Export.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" @@ -376,6 +377,7 @@ int* errno_slot() { extern "C" std::size_t NetCtlHostDnsServers_nid_no_patch(std::uint32_t* servers, std::size_t capacity); extern "C" bool NetCtlHostIpv4Address_nid_no_patch(std::uint32_t* address, std::uint32_t* netmask, std::uint32_t* mtu); +extern "C" bool NetCtlHostIpv4Gateway_nid_no_patch(std::uint32_t* gateway); std::mutex g_dnsLock; bool g_dnsOverridden = false; @@ -2761,8 +2763,7 @@ int APS5_VABI sceNetGetRouteInfo(std::int32_t family, NetRouteInfo* routes, std: } int APS5_VABI sceNetGetsockoptEx(int socketId, int level, int option, void* value, std::uint32_t* valueSize) { - NotImplemented_nid_no_patch(__func__); - return 0; + return sceNetGetsockopt(socketId, level, option, value, valueSize); } void APS5_VABI sceNetGetSystemTime(std::uint64_t* output) { @@ -2866,13 +2867,11 @@ int APS5_VABI sceNetSetDnsInfoToKernel(const NetDnsInfo* info, std::uint32_t fla } int APS5_VABI sceNetSetsockoptEx(int socketId, int level, int option, const void* value, std::uint32_t valueSize) { - NotImplemented_nid_no_patch(__func__); - return 0; + return sceNetSetsockopt(socketId, level, option, value, valueSize); } int APS5_VABI sceNetSocketInternal(const char* name, int domain, int type, int protocol) { - NotImplemented_nid_no_patch(__func__); - return 0; + return sceNetSocket(name, domain, type, protocol); } int APS5_VABI sceNetSyncCreate(NetSyncObject* sync, std::uint32_t signals, const char* name) { @@ -2901,8 +2900,8 @@ int APS5_VABI sceNetSyncWait(NetSyncObject* sync, std::uint32_t* timeout) { } int APS5_VABI sceNetSysctl(const int* name, std::uint32_t nameLength, void* oldValue, std::size_t* oldSize, const void* newValue, std::size_t newSize) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (sysctl_nid_postfix(name, nameLength, oldValue, oldSize, newValue, newSize) == 0) return 0; + return fail(*__error_nid_postfix()); } int APS5_VABI sceNetThreadCreate(Pthread* thread, const NetThreadParameter* parameter, const char* name) { @@ -2960,13 +2959,45 @@ int APS5_VABI sceNetShowNetstatExForBuffer(char* buffer, std::int32_t bufferSize } const char* APS5_VABI sceNetInetNtopWithScopeId(std::int32_t family, const void* address, char* text, std::uint32_t size, std::uint32_t scopeId) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (address == nullptr || text == nullptr) { + set_errno(NET_EINVAL); + return nullptr; + } + if (family != NET_AF_INET6) throw std::runtime_error("sceNetInetNtopWithScopeId: family " + std::to_string(family) + " is not implemented"); + std::array unscoped{}; + if (sceNetInetNtop(family, address, unscoped.data(), static_cast(unscoped.size())) == nullptr) return nullptr; + std::string scoped(unscoped.data()); + if (scopeId != 0) scoped += "%" + std::to_string(scopeId); + if (scoped.size() >= size) { + set_errno(NET_ENOSPC); + return nullptr; + } + std::memcpy(text, scoped.c_str(), scoped.size() + 1); + return text; } int APS5_VABI sceNetInetPtonWithScopeId(std::int32_t family, const char* text, void* address, std::uint32_t* scopeId) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (text == nullptr || address == nullptr || scopeId == nullptr) return fail(NET_EINVAL); + if (family != NET_AF_INET6) throw std::runtime_error("sceNetInetPtonWithScopeId: family " + std::to_string(family) + " is not implemented"); + const char* percent = std::strchr(text, '%'); + std::uint64_t zone = 0; + if (percent != nullptr) { + const std::string_view digits(percent + 1); + if (digits.empty()) return 0; + if (!std::all_of(digits.begin(), digits.end(), [](char digit) { return digit >= '0' && digit <= '9'; })) + throw std::runtime_error("sceNetInetPtonWithScopeId: the interface name zone in " + std::string(text) + " is not implemented"); + for (const char digit : digits) { + zone = zone * 10 + static_cast(digit - '0'); + if (zone > 0xffffffffull) return 0; + } + } + const std::string unscoped = percent != nullptr ? std::string(text, percent) : std::string(text); + std::array parsed{}; + const int converted = sceNetInetPton(family, unscoped.c_str(), parsed.data()); + if (converted != 1) return converted; + std::memcpy(address, parsed.data(), parsed.size()); + *scopeId = static_cast(zone); + return 1; } int APS5_VABI sceNetGetDns6Info(NetDns6Info* info, std::uint32_t flags) { @@ -3132,7 +3163,10 @@ int APS5_VABI sceNetConfigDelRoute(const std::uint32_t* destination, const std:: } int APS5_VABI sceNetConfigGetDefaultRoute(std::uint32_t* gateway) { - NotImplemented_nid_no_patch(__func__); + if (!gateway) throw std::runtime_error("sceNetConfigGetDefaultRoute: a null gateway is not supported"); + std::uint32_t found = 0; + if (!NetCtlHostIpv4Gateway_nid_no_patch(&found)) throw std::runtime_error("sceNetConfigGetDefaultRoute: the answer without an IPv4 default route is not implemented"); + *gateway = found; return 0; } diff --git a/core/libs/prx/libSceNetCtl/Export.cpp b/core/libs/prx/libSceNetCtl/Export.cpp index 43277e29bf2..46e39d2b405 100644 --- a/core/libs/prx/libSceNetCtl/Export.cpp +++ b/core/libs/prx/libSceNetCtl/Export.cpp @@ -482,6 +482,13 @@ extern "C" std::size_t NetCtlHostDnsServers_nid_no_patch(std::uint32_t* servers, return host.dns.size(); } +extern "C" bool NetCtlHostIpv4Gateway_nid_no_patch(std::uint32_t* gateway) { + HostIpv4Interface host; + if (!FindHostIpv4Interface(host) || host.gateway.s_addr == 0) return false; + *gateway = host.gateway.s_addr; + return true; +} + #ifdef _WIN32 using StunSocket = SOCKET; static constexpr StunSocket INVALID_STUN_SOCKET = INVALID_SOCKET; diff --git a/core/libs/tests/GuestSceNet.cpp b/core/libs/tests/GuestSceNet.cpp index 4757037aaf5..941de1c7175 100644 --- a/core/libs/tests/GuestSceNet.cpp +++ b/core/libs/tests/GuestSceNet.cpp @@ -331,6 +331,114 @@ static void CheckIfName() { Require(missing != nullptr && missing[0] == '\0' && *sceNetErrnoLoc() == 22); } +static void CheckExForwards() { + const int internal = sceNetSocketInternal("internal", 2, 2, 17); + Require(internal >= 0); + const int enabled = 1; + Require(sceNetSetsockoptEx(internal, 0xffff, 0x4, &enabled, sizeof(enabled)) == 0); + int option = 0; + std::uint32_t optionSize = sizeof(option); + Require(sceNetGetsockoptEx(internal, 0xffff, 0x4, &option, &optionSize) == 0); + int publicOption = 0; + std::uint32_t publicSize = sizeof(publicOption); + Require(sceNetGetsockopt(internal, 0xffff, 0x4, &publicOption, &publicSize) == 0); + Require(option == publicOption && option != 0 && optionSize == publicSize); + const KernelTimeval timeout{0, 250000}; + Require(sceNetSetsockoptEx(internal, 0xffff, 0x1006, &timeout, sizeof(timeout)) == 0); + KernelTimeval read{}; + std::uint32_t readSize = sizeof(read); + Require(sceNetGetsockoptEx(internal, 0xffff, 0x1006, &read, &readSize) == 0); + Require(readSize == sizeof(read) && read.tv_sec == 0 && read.tv_usec == 250000); + Require(sceNetSocketClose(internal) == 0); + for (const int closed : {internal, -1}) { + optionSize = sizeof(option); + const int ex = sceNetGetsockoptEx(closed, 0xffff, 0x4, &option, &optionSize); + const int exError = *sceNetErrnoLoc(); + Require(ex < 0 && ex == sceNetGetsockopt(closed, 0xffff, 0x4, &option, &optionSize) && exError == *sceNetErrnoLoc()); + const int set = sceNetSetsockoptEx(closed, 0xffff, 0x4, &enabled, sizeof(enabled)); + const int setError = *sceNetErrnoLoc(); + Require(set < 0 && set == sceNetSetsockopt(closed, 0xffff, 0x4, &enabled, sizeof(enabled)) && setError == *sceNetErrnoLoc()); + } + for (const auto& [family, type] : {std::pair{1, 2}, std::pair{2, 9}}) { + const int internalResult = sceNetSocketInternal(nullptr, family, type, 0); + const int internalError = *sceNetErrnoLoc(); + Require(internalResult < 0 && internalResult == sceNetSocket(nullptr, family, type, 0) && internalError == *sceNetErrnoLoc()); + } +} + +static void CheckSysctl() { + const std::array pageSize{6, 7}; + int kernelValue = 0; + std::size_t kernelSize = sizeof(kernelValue); + Require(sysctl_nid_postfix(pageSize.data(), 2, &kernelValue, &kernelSize, nullptr, 0) == 0); + int netValue = 0; + std::size_t netSize = sizeof(netValue); + Require(sceNetSysctl(pageSize.data(), 2, &netValue, &netSize, nullptr, 0) == 0); + Require(netValue == kernelValue && netSize == kernelSize && netValue > 0); + std::size_t probe = 0; + Require(sceNetSysctl(pageSize.data(), 2, nullptr, &probe, nullptr, 0) == 0 && probe == sizeof(int)); + Require(Failed(sceNetSysctl(pageSize.data(), 1, &netValue, &netSize, nullptr, 0), 22)); + Require(Failed(sceNetSysctl(nullptr, 2, &netValue, &netSize, nullptr, 0), 14)); + Require(Failed(sceNetSysctl(pageSize.data(), 2, &netValue, &netSize, &netValue, sizeof(netValue)), 1)); + std::uint8_t truncated = 0; + std::size_t truncatedSize = sizeof(truncated); + Require(Failed(sceNetSysctl(pageSize.data(), 2, &truncated, &truncatedSize, nullptr, 0), 12) && truncatedSize == sizeof(truncated)); +} + +static void CheckScopeIdText() { + std::array address{}; + std::uint32_t scope = 0xa5a5a5a5u; + Require(sceNetInetPtonWithScopeId(28, "fe80::1%2", address.data(), &scope) == 1 && scope == 2); + std::array plain{}; + Require(sceNetInetPton(28, "fe80::1", plain.data()) == 1 && address == plain); + std::array text{}; + Require(sceNetInetNtopWithScopeId(28, address.data(), text.data(), text.size(), scope) == text.data()); + Require(std::strcmp(text.data(), "fe80::1%2") == 0); + text.fill('x'); + *sceNetErrnoLoc() = 123; + Require(sceNetInetNtopWithScopeId(28, address.data(), text.data(), 9, 2) == nullptr && *sceNetErrnoLoc() == 28 && text[0] == 'x'); + Require(sceNetInetNtopWithScopeId(28, address.data(), text.data(), 10, 2) == text.data() && std::strcmp(text.data(), "fe80::1%2") == 0); + Require(sceNetInetNtopWithScopeId(28, address.data(), text.data(), text.size(), 0) == text.data() && std::strcmp(text.data(), "fe80::1") == 0); + scope = 7; + Require(sceNetInetPtonWithScopeId(28, "fe80::1", address.data(), &scope) == 1 && scope == 0 && address == plain); + Require(sceNetInetPtonWithScopeId(28, "ff02::1%4294967295", address.data(), &scope) == 1 && scope == 0xffffffffu); + Require(sceNetInetNtopWithScopeId(28, address.data(), text.data(), text.size(), scope) == text.data()); + Require(std::strcmp(text.data(), "ff02::1%4294967295") == 0); + for (const char* rejected : {"fe80::1%", "fe80::1%4294967296", "fe80::g%2", "1.2.3.4%2"}) { + address.fill(0xa5); + scope = 0xa5a5a5a5u; + Require(sceNetInetPtonWithScopeId(28, rejected, address.data(), &scope) == 0); + Require(scope == 0xa5a5a5a5u && address[0] == 0xa5 && address[15] == 0xa5); + } + Require(Failed(sceNetInetPtonWithScopeId(28, "fe80::1%2", address.data(), nullptr), 22)); + *sceNetErrnoLoc() = 123; + Require(sceNetInetNtopWithScopeId(28, nullptr, text.data(), text.size(), 2) == nullptr && *sceNetErrnoLoc() == 22); +} + +static void CheckDefaultRoute() { + NetCtlInfo info{}; + const int result = sceNetCtlGetInfo(16, &info); + const char* route = reinterpret_cast(info.opaque); + std::uint32_t gateway = 0xa5a5a5a5u; + const auto throws = [](auto&& call) { + try { + call(); + } catch (const std::runtime_error&) { + return true; + } + return false; + }; + Require(throws([] { sceNetConfigGetDefaultRoute(nullptr); })); + if (result != 0 || std::strcmp(route, "0.0.0.0") == 0) { + Require(result == 0 || result == static_cast(0x80412108)); + return; + } + Require(sceNetConfigGetDefaultRoute(&gateway) == 0); + std::array text{}; + Require(sceNetInetNtop(2, &gateway, text.data(), text.size()) == text.data()); + Require(std::strcmp(text.data(), route) == 0); +} + static void CheckRandom() { Require(Failed(sceNetGetRandom(nullptr), 22)); std::array values{}; @@ -2187,6 +2295,9 @@ int main() { CheckPoolStats(); CheckStatisticsInfo(); CheckIfName(); + CheckExForwards(); + CheckSysctl(); + CheckScopeIdText(); CheckRandom(); CheckByteOrderConversion(); CheckNetMemory(); @@ -2641,6 +2752,7 @@ int main() { Require(sceNetCtlGetState(&state) == 0); Require(state == 0 || state == 3); Require(sceNetCtlGetIfStat(nullptr) == static_cast(0x80412107)); + CheckDefaultRoute(); { const char* counterPath = "guest_sce_net_byte_counter.txt"; std::FILE* counter = std::fopen(counterPath, "w"); diff --git a/docs/dev/debt/libSceNet.md b/docs/dev/debt/libSceNet.md index f54727a8509..295c2df966a 100644 --- a/docs/dev/debt/libSceNet.md +++ b/docs/dev/debt/libSceNet.md @@ -30,6 +30,10 @@ - [sceNetPppoeStart](../../../core/libs/prx/libSceNet/Export.cpp): seven arguments are confirmed, including the parameter pointer on the stack. The 272-byte parameter contains two DNS addresses, two 128-byte credential arrays, MTU and flags. Callback arguments are user data, event and error; the two unused thread-option meanings remain unresolved. - [sceNetConfigRoutingStart](../../../core/libs/prx/libSceNet/Export.cpp): five pointer/scalar arguments are confirmed. Input address data is eight bytes; the mutable server parameter is 20 bytes and duplicates its first address when its count is one. Optional rules contain a count and pointer to 20-byte records with protocol, two address/port pairs and flags. Layouts are defined; rule flag meanings and protocol-specific port interpretation remain unresolved. - [sceNetConfigRoutingShowRoutingConfig](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - the native routine consumes no arguments; the throwing implementation remains unchanged. +- [sceNetSocketInternal, sceNetGetsockoptEx and sceNetSetsockoptEx](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - forward to sceNetSocket, sceNetGetsockopt and sceNetSetsockopt. Their restored argument lists match the public ones, and nothing in the tree records how the console tells these entry points apart from the public ones (for example by the calling process). +- [sceNetSysctl](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - calls libkernel's sysctl with the FreeBSD sysctl(3) argument list, so it answers exactly the MIBs libkernel models, and reports a failure as the library's other calls do (`0x80410100 | errno`, with the errno set). How the console's routine reports errors is not measured. +- [sceNetInetNtopWithScopeId and sceNetInetPtonWithScopeId](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - convert IPv6 text with a numeric zone (`fe80::1%2`, [RFC 4007 section 11](https://www.rfc-editor.org/rfc/rfc4007#section-11)) through sceNetInetNtop and sceNetInetPton, as FreeBSD 11.4's `getnameinfo` with `NI_NUMERICSCOPE` prints it and `getaddrinfo` parses it. A zone given as an interface name, and `AF_INET`, throw: the tree has no mapping from interface names to the console's interface indexes, and no answer for an IPv4 zone. +- [sceNetConfigGetDefaultRoute](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - reports the IPv4 gateway of the host interface that sceNetCtlGetInfo describes (`DEFAULT_ROUTE`), in network byte order, the order sceNetInetPton writes an `AF_INET` address in. With no gateway, or a null pointer, it throws: the console's answer for a missing route is not measured. ### Functional