diff --git a/core/libs/prx/libSceNet/CMakeLists.txt b/core/libs/prx/libSceNet/CMakeLists.txt index 54c026e47c..6076a98df3 100644 --- a/core/libs/prx/libSceNet/CMakeLists.txt +++ b/core/libs/prx/libSceNet/CMakeLists.txt @@ -124,6 +124,17 @@ if(BUILD_TESTING) set_tests_properties(guest_sce_net_keepalive PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_net_kernel_forwards_tests ${LIBS_INCLUDE_DIR}/tests/GuestNetKernelForwards.cpp) + target_include_directories(guest_net_kernel_forwards_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_net_kernel_forwards_tests PRIVATE libSceNet libkernel libc) + configure_windows_unwind(guest_net_kernel_forwards_tests) + add_test(NAME guest_sce_net_kernel_forwards COMMAND guest_net_kernel_forwards_tests) + set_tests_properties(guest_sce_net_kernel_forwards PROPERTIES TIMEOUT 20) + if(WIN32) + target_link_libraries(guest_net_kernel_forwards_tests PRIVATE ws2_32) + set_tests_properties(guest_sce_net_kernel_forwards PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_datagram_receive_tests ${LIBS_INCLUDE_DIR}/tests/GuestDatagramReceive.cpp) target_include_directories(guest_datagram_receive_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_datagram_receive_tests PRIVATE libSceNet libkernel libc) diff --git a/core/libs/prx/libSceNet/Export.cpp b/core/libs/prx/libSceNet/Export.cpp index 958a4cfaf1..389b3652ca 100644 --- a/core/libs/prx/libSceNet/Export.cpp +++ b/core/libs/prx/libSceNet/Export.cpp @@ -928,6 +928,42 @@ bool GetSocketOptionForKernel(int descriptor, int level, int option, void* value const bool g_socketOptionGetterRegistered = (KernelSetSocketOptionGetter_nid_no_patch(&GetSocketOptionForKernel), true); +bool OwnsSocket(int descriptor) { + std::lock_guard lk(g_mutex); + return g_socks.count(descriptor) != 0; +} + +int ErrorOf(std::int64_t result) { + return result < 0 ? *errno_slot() : 0; +} + +const KernelSocketPoll::Forwards g_socketForwards{ + [](int descriptor, const void* address, std::uint32_t length, int* error) { + if (!OwnsSocket(descriptor)) return false; + *error = ErrorOf(sceNetBind_nid_postfix(descriptor, address, length)); + return true; + }, + [](int descriptor, void* address, std::uint32_t* length, int* error) { + if (!OwnsSocket(descriptor)) return false; + *error = ErrorOf(sceNetGetsockname(descriptor, address, length)); + return true; + }, + [](int descriptor, const void* buffer, std::uint64_t length, int flags, const void* address, std::uint32_t addressLength, std::int64_t* result, int* error) { + if (!OwnsSocket(descriptor)) return false; + *result = sceNetSendto(descriptor, buffer, static_cast(length), flags, address, addressLength); + *error = ErrorOf(*result); + return true; + }, + [](int descriptor, void* buffer, std::uint64_t length, int flags, void* address, std::uint32_t* addressLength, std::int64_t* result, int* error) { + if (!OwnsSocket(descriptor)) return false; + *result = sceNetRecvfrom(descriptor, buffer, static_cast(length), flags, address, addressLength); + *error = ErrorOf(*result); + return true; + }, +}; + +const bool g_socketForwardsRegistered = (KernelSetSocketForwards_nid_no_patch(&g_socketForwards), true); + constexpr int GuestFcntlGetStatus = 3; constexpr int GuestFcntlSetStatus = 4; constexpr int GuestReadWrite = 2; diff --git a/core/libs/prx/libkernel/Socket/include/SocketPoll.hpp b/core/libs/prx/libkernel/Socket/include/SocketPoll.hpp index 55956c9294..15b5fdbfd9 100644 --- a/core/libs/prx/libkernel/Socket/include/SocketPoll.hpp +++ b/core/libs/prx/libkernel/Socket/include/SocketPoll.hpp @@ -21,6 +21,12 @@ using Closer = bool (*)(int descriptor, int* error); using OptionSetter = bool (*)(int descriptor, int level, int option, const void* value, std::uint32_t length, int* error); using OptionGetter = bool (*)(int descriptor, int level, int option, void* value, std::uint32_t* length, int* error); using StatusControl = bool (*)(int descriptor, int command, int flags, int* result, int* error); +struct Forwards { + bool (*bind)(int descriptor, const void* address, std::uint32_t length, int* error); + bool (*getsockname)(int descriptor, void* address, std::uint32_t* length, int* error); + bool (*sendto)(int descriptor, const void* buffer, std::uint64_t length, int flags, const void* address, std::uint32_t addressLength, std::int64_t* result, int* error); + bool (*recvfrom)(int descriptor, void* buffer, std::uint64_t length, int flags, void* address, std::uint32_t* addressLength, std::int64_t* result, int* error); +}; struct NativeOwner { int (*transfer)(NativeOwner* owner, bool send, void* buffer, int length) = nullptr; }; @@ -28,6 +34,7 @@ struct NativeOwner { using NativeLookup = bool (*)(int descriptor, std::shared_ptr* owner, std::uintptr_t* native); } +extern "C" void KernelSetSocketForwards_nid_no_patch(const KernelSocketPoll::Forwards* forwards); extern "C" void KernelSetSocketPoller_nid_no_patch(KernelSocketPoll::Poller poller); extern "C" void KernelSetSocketCloser_nid_no_patch(KernelSocketPoll::Closer closer); extern "C" bool KernelCloseSocket_nid_no_patch(int descriptor, int* error); diff --git a/core/libs/prx/libkernel/Socket/src/Udp.cpp b/core/libs/prx/libkernel/Socket/src/Udp.cpp index d652759f0e..9b62ca5f82 100644 --- a/core/libs/prx/libkernel/Socket/src/Udp.cpp +++ b/core/libs/prx/libkernel/Socket/src/Udp.cpp @@ -270,6 +270,10 @@ int Install(std::shared_ptr socket) { } return number; } +std::atomic socketForwards{nullptr}; +const KernelSocketPoll::Forwards* ForwardsFor(int descriptor) { + return GuestSockets::IsOpen(descriptor) ? nullptr : socketForwards.load(std::memory_order_acquire); +} std::shared_ptr Lookup(int descriptor) { std::lock_guard lock(socketsMutex); const auto found = sockets.find(descriptor); @@ -652,6 +656,10 @@ extern "C" void KernelReleaseSocketNumber_nid_no_patch(int number) { ReleaseNumber(number); } +extern "C" void KernelSetSocketForwards_nid_no_patch(const KernelSocketPoll::Forwards* forwards) { + socketForwards.store(forwards, std::memory_order_release); +} + namespace { #ifdef _WIN32 using NativePollDescriptor = WSAPOLLFD; @@ -1545,6 +1553,8 @@ std::int64_t APS5_VABI recv_nid_postfix(int descriptor, void* buffer, std::uint6 return result < 0 ? Fail(NativeError()) : result; } int APS5_VABI bind_nid_postfix(int descriptor, const void* address, std::uint32_t length) { + int netError = 0; + if (const auto* forwards = ForwardsFor(descriptor); forwards != nullptr && forwards->bind(descriptor, address, length, &netError)) return netError == 0 ? 0 : Fail(netError); if (const int error = AddressLengthError(address, length)) return Fail(error); const auto socket = Lookup(descriptor); if (!socket) return -1; @@ -1608,6 +1618,8 @@ int CopyAddressOut(const sockaddr_storage& native, bool unnamed, void* address, int APS5_VABI getsockname_nid_postfix(int descriptor, void* address, std::uint32_t* length) { std::uint32_t capacity = 0; if (!GuestCopyIn_nid_no_patch(&capacity, length, sizeof(capacity))) return Fail(14); + int netError = 0; + if (const auto* forwards = ForwardsFor(descriptor); forwards != nullptr && forwards->getsockname(descriptor, address, length, &netError)) return netError == 0 ? 0 : Fail(netError); const auto socket = Lookup(descriptor); if (!socket) return -1; if (socket->family == GuestUnix) return CopyAddressOut({}, true, address, length, capacity); @@ -1674,6 +1686,9 @@ int APS5_VABI _ioctl_nid_postfix(int descriptor, std::uint64_t request, void* ar } std::int64_t APS5_VABI sendto_nid_postfix(int descriptor, const void* buffer, std::uint64_t length, int flags, const void* address, std::uint32_t addressLength) { + std::int64_t netResult = 0; + int netError = 0; + if (const auto* forwards = ForwardsFor(descriptor); forwards != nullptr && forwards->sendto(descriptor, buffer, length, flags, address, addressLength, &netResult, &netError)) return netError == 0 ? netResult : Fail(netError); std::array destinationCopy; if (address) { if (const int error = AddressLengthError(address, addressLength)) return Fail(error); @@ -1711,6 +1726,9 @@ std::int64_t APS5_VABI sendto_nid_postfix(int descriptor, const void* buffer, st } std::int64_t APS5_VABI recvfrom_nid_postfix(int descriptor, void* buffer, std::uint64_t length, int flags, void* address, std::uint32_t* addressLength) { + std::int64_t netResult = 0; + int netError = 0; + if (const auto* forwards = ForwardsFor(descriptor); forwards != nullptr && forwards->recvfrom(descriptor, buffer, length, flags, address, addressLength, &netResult, &netError)) return netError == 0 ? netResult : Fail(netError); std::uint32_t capacity = 0; if (addressLength && !GuestCopyIn_nid_no_patch(&capacity, addressLength, sizeof(capacity))) return Fail(14); const auto socket = Lookup(descriptor); diff --git a/core/libs/tests/GuestNetKernelForwards.cpp b/core/libs/tests/GuestNetKernelForwards.cpp new file mode 100644 index 0000000000..9c24837fe8 --- /dev/null +++ b/core/libs/tests/GuestNetKernelForwards.cpp @@ -0,0 +1,82 @@ +#include +#include +#include +#include +#include "prx/libSceNet/libSceNet.hpp" +#include "prx/libkernel/libkernel.hpp" + +static void Require(bool condition) { + if (!condition) std::abort(); +} + +static bool Failed(std::int64_t result, int error) { + return result == -1 && *__error_nid_postfix() == error; +} + +static void ClearError() { + *__error_nid_postfix() = 0; +} + +static void CheckForwardedDatagram() { + const int receiver = sceNetSocket("forward-receiver", 2, 2, 17); + const int sender = sceNetSocket("forward-sender", 2, 2, 17); + Require(receiver >= 0 && sender >= 0); + std::array local{16, 2, 0, 0, 127, 0, 0, 1}; + Require(bind_nid_postfix(receiver, local.data(), local.size()) == 0); + Require(bind_nid_postfix(sender, local.data(), local.size()) == 0); + std::array receiverAddress{}; + std::array senderAddress{}; + std::uint32_t size = receiverAddress.size(); + Require(getsockname_nid_postfix(receiver, receiverAddress.data(), &size) == 0 && size == receiverAddress.size()); + Require((receiverAddress[2] | receiverAddress[3]) != 0); + size = senderAddress.size(); + Require(getsockname_nid_postfix(sender, senderAddress.data(), &size) == 0 && size == senderAddress.size()); + const char payload[] = "ping"; + Require(sendto_nid_postfix(sender, payload, sizeof(payload), 0, receiverAddress.data(), receiverAddress.size()) == sizeof(payload)); + char received[8]{}; + std::array from{}; + size = from.size(); + Require(recvfrom_nid_postfix(receiver, received, sizeof(received), 0, from.data(), &size) == sizeof(payload)); + Require(std::strcmp(received, payload) == 0 && size == from.size()); + Require(from[2] == senderAddress[2] && from[3] == senderAddress[3]); + Require(sceNetSocketClose(receiver) == 0 && sceNetSocketClose(sender) == 0); +} + +static void CheckForwardedErrors() { + const int socket = sceNetSocket("forward-errors", 2, 2, 17); + Require(socket >= 0); + std::array local{16, 2, 0, 0, 127, 0, 0, 1}; + ClearError(); + Require(Failed(bind_nid_postfix(socket, local.data(), 1), 22)); + Require(bind_nid_postfix(socket, local.data(), local.size()) == 0); + char received[8]{}; + ClearError(); + Require(Failed(recvfrom_nid_postfix(socket, received, sizeof(received), 0x80, nullptr, nullptr), 35)); + ClearError(); + Require(Failed(getsockname_nid_postfix(socket, nullptr, nullptr), 22)); + ClearError(); + Require(Failed(sendto_nid_postfix(socket, "x", 1, 0, local.data(), 1), 22)); + Require(sceNetSocketClose(socket) == 0); +} + +static void CheckUnknownDescriptor() { + constexpr int unknown = 0x7ffffff0; + std::array local{16, 2, 0, 0, 127, 0, 0, 1}; + std::uint32_t size = local.size(); + char byte = 0; + ClearError(); + Require(Failed(bind_nid_postfix(unknown, local.data(), local.size()), 9)); + ClearError(); + Require(Failed(getsockname_nid_postfix(unknown, local.data(), &size), 9)); + ClearError(); + Require(Failed(sendto_nid_postfix(unknown, &byte, 1, 0, local.data(), local.size()), 9)); + ClearError(); + Require(Failed(recvfrom_nid_postfix(unknown, &byte, 1, 0, nullptr, nullptr), 9)); +} + +int main() { + CheckForwardedDatagram(); + CheckForwardedErrors(); + CheckUnknownDescriptor(); + return 0; +}