mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-29 12:15:39 +00:00
fix(libkernel): report SO_ERROR and the other SOL_SOCKET options like FreeBSD
getsockopt and setsockopt on a libkernel socket knew four SOL_SOCKET options (SO_REUSEADDR, SO_BROADCAST, SO_SNDBUF, SO_RCVBUF) and failed every other one with ENOPROTOOPT. The Mound's EOS SDK reads SO_ERROR on two of its sockets after a connect, which therefore always failed. The SOL_SOCKET options now follow FreeBSD 11.4 sosetopt and sogetopt: - SO_ERROR returns the pending error of the socket as a FreeBSD errno and reports it once (Winsock does not clear it on read, so the library does). - SO_TYPE, SO_ACCEPTCONN, SO_KEEPALIVE, SO_DONTROUTE, SO_OOBINLINE and SO_LINGER are read from and set on the host socket; the boolean options read back as their own bit, as FreeBSD returns so_options & option. - SO_DEBUG, SO_USELOOPBACK, SO_REUSEPORT, SO_TIMESTAMP and SO_NOSIGPIPE stay ENOPROTOOPT until a title needs one. - A value is read from its first int and fails with EINVAL when shorter, a getsockopt buffer is filled up to its length (sooptcopyin, sooptcopyout), SO_SNDBUF and SO_RCVBUF below 1 fail with EINVAL, and SO_LINGER outside 0..65535 seconds fails with EDOM. A connect on a non-blocking socket now fails with EINPROGRESS on Windows too (it was EWOULDBLOCK, which a title waiting for the connection does not expect). Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
This commit is contained in:
1 parent
1e7f4967ed
commit
79e162110a
6 files changed
+424
-25
No files matched your search
@@ -43,6 +43,7 @@ add_library(libkernel SHARED EXCLUDE_FROM_ALL
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Pthread/Posix/Spinlock.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Socket/src/Socket.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Socket/src/Udp.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Socket/src/SocketOptions.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Socket/src/AddressConversion.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/EventFlag/src/EventFlag.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Module/src/Module.cpp
|
||||
@@ -984,6 +985,16 @@ if(BUILD_TESTING)
|
||||
target_include_directories(guest_socket_error_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
configure_windows_unwind(guest_socket_error_tests)
|
||||
add_test(NAME guest_socket_error COMMAND guest_socket_error_tests)
|
||||
add_test_executable(guest_socket_options_tests ${LIBS_INCLUDE_DIR}/tests/GuestSocketOptions.cpp)
|
||||
target_include_directories(guest_socket_options_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_socket_options_tests PRIVATE libkernel libc)
|
||||
configure_windows_unwind(guest_socket_options_tests)
|
||||
add_test(NAME guest_socket_options COMMAND guest_socket_options_tests)
|
||||
set_tests_properties(guest_socket_options PROPERTIES TIMEOUT 30)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_socket_options PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
|
||||
endif()
|
||||
add_test_executable(guest_udp_tests ${LIBS_INCLUDE_DIR}/tests/GuestUdp.cpp)
|
||||
target_include_directories(guest_udp_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_udp_tests PRIVATE libkernel libc)
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
namespace GuestSockets {
|
||||
|
||||
struct SocketOptionState {
|
||||
int reportedError = 0;
|
||||
};
|
||||
|
||||
int SetSocketLevelOption(std::uintptr_t native, SocketOptionState& state, int option, const void* value, std::uint32_t length);
|
||||
int GetSocketLevelOption(std::uintptr_t native, SocketOptionState& state, int type, int option, void* value, std::uint32_t* length);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#else
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
#include "prx/libkernel/Socket/include/SocketError.hpp"
|
||||
#include "prx/libkernel/Socket/include/SocketOptions.hpp"
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
namespace GuestSockets {
|
||||
namespace {
|
||||
|
||||
constexpr int GuestAcceptConnection = 0x2;
|
||||
constexpr int GuestReuseAddress = 0x4;
|
||||
constexpr int GuestKeepAlive = 0x8;
|
||||
constexpr int GuestDontRoute = 0x10;
|
||||
constexpr int GuestBroadcast = 0x20;
|
||||
constexpr int GuestLinger = 0x80;
|
||||
constexpr int GuestOobInline = 0x100;
|
||||
constexpr int GuestSendBuffer = 0x1001;
|
||||
constexpr int GuestReceiveBuffer = 0x1002;
|
||||
constexpr int GuestError = 0x1007;
|
||||
constexpr int GuestType = 0x1008;
|
||||
|
||||
constexpr int ErrorInvalid = 22;
|
||||
constexpr int ErrorDomain = 33;
|
||||
constexpr int ErrorNoProtocolOption = 42;
|
||||
constexpr int MaximumLinger = 65535;
|
||||
|
||||
struct GuestLingerValue {
|
||||
std::int32_t enabled;
|
||||
std::int32_t seconds;
|
||||
};
|
||||
|
||||
#ifdef _WIN32
|
||||
using NativeHandle = SOCKET;
|
||||
using NativeOptionValue = const char*;
|
||||
using NativeOptionOutput = char*;
|
||||
int HostError() { return GuestErrorFromHost(WSAGetLastError()); }
|
||||
#else
|
||||
using NativeHandle = int;
|
||||
using NativeOptionValue = const void*;
|
||||
using NativeOptionOutput = void*;
|
||||
int HostError() { return GuestErrorFromHost(errno); }
|
||||
#endif
|
||||
|
||||
int NativeBoolean(int option) {
|
||||
switch (option) {
|
||||
case GuestReuseAddress: return SO_REUSEADDR;
|
||||
case GuestKeepAlive: return SO_KEEPALIVE;
|
||||
case GuestDontRoute: return SO_DONTROUTE;
|
||||
case GuestBroadcast: return SO_BROADCAST;
|
||||
case GuestOobInline: return SO_OOBINLINE;
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int Copy(void* output, std::uint32_t* length, const void* value, std::uint32_t size) {
|
||||
const std::uint32_t count = std::min(*length, size);
|
||||
if (count != 0 && !output) return ErrorInvalid;
|
||||
if (count != 0) std::memcpy(output, value, count);
|
||||
*length = count;
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int SetSocketLevelOption(std::uintptr_t native, SocketOptionState& state, int option, const void* value, std::uint32_t length) {
|
||||
const auto handle = static_cast<NativeHandle>(native);
|
||||
if (option == GuestLinger) {
|
||||
if (length < sizeof(GuestLingerValue)) return ErrorInvalid;
|
||||
if (!value) return ErrorInvalid;
|
||||
GuestLingerValue guest;
|
||||
std::memcpy(&guest, value, sizeof(guest));
|
||||
if (guest.seconds < 0 || guest.seconds > MaximumLinger) return ErrorDomain;
|
||||
#ifdef _WIN32
|
||||
linger request{};
|
||||
request.l_onoff = guest.enabled != 0 ? 1 : 0;
|
||||
request.l_linger = static_cast<u_short>(guest.seconds);
|
||||
#else
|
||||
linger request{};
|
||||
request.l_onoff = guest.enabled != 0 ? 1 : 0;
|
||||
request.l_linger = guest.seconds;
|
||||
#endif
|
||||
if (::setsockopt(handle, SOL_SOCKET, SO_LINGER, reinterpret_cast<NativeOptionValue>(&request), sizeof(request))) return HostError();
|
||||
return 0;
|
||||
}
|
||||
const bool native_boolean = NativeBoolean(option) != -1;
|
||||
if (native_boolean || option == GuestSendBuffer || option == GuestReceiveBuffer) {
|
||||
if (length < sizeof(int)) return ErrorInvalid;
|
||||
if (!value) return ErrorInvalid;
|
||||
int number;
|
||||
std::memcpy(&number, value, sizeof(number));
|
||||
if (option == GuestSendBuffer || option == GuestReceiveBuffer) {
|
||||
if (number < 1) return ErrorInvalid;
|
||||
const int nativeOption = option == GuestSendBuffer ? SO_SNDBUF : SO_RCVBUF;
|
||||
if (::setsockopt(handle, SOL_SOCKET, nativeOption, reinterpret_cast<NativeOptionValue>(&number), sizeof(number))) return HostError();
|
||||
return 0;
|
||||
}
|
||||
const int enabled = number != 0 ? 1 : 0;
|
||||
if (::setsockopt(handle, SOL_SOCKET, NativeBoolean(option), reinterpret_cast<NativeOptionValue>(&enabled), sizeof(enabled))) return HostError();
|
||||
return 0;
|
||||
}
|
||||
return ErrorNoProtocolOption;
|
||||
}
|
||||
|
||||
int GetSocketLevelOption(std::uintptr_t native, SocketOptionState& state, int type, int option, void* value, std::uint32_t* length) {
|
||||
const auto handle = static_cast<NativeHandle>(native);
|
||||
if (option == GuestLinger) {
|
||||
linger current{};
|
||||
socklen_t size = sizeof(current);
|
||||
if (::getsockopt(handle, SOL_SOCKET, SO_LINGER, reinterpret_cast<NativeOptionOutput>(¤t), &size)) return HostError();
|
||||
const GuestLingerValue guest{current.l_onoff != 0 ? GuestLinger : 0, static_cast<std::int32_t>(current.l_linger)};
|
||||
return Copy(value, length, &guest, sizeof(guest));
|
||||
}
|
||||
int number = 0;
|
||||
if (const int nativeOption = NativeBoolean(option); nativeOption != -1) {
|
||||
int enabled = 0;
|
||||
socklen_t size = sizeof(enabled);
|
||||
if (::getsockopt(handle, SOL_SOCKET, nativeOption, reinterpret_cast<NativeOptionOutput>(&enabled), &size)) return HostError();
|
||||
number = enabled != 0 ? option : 0;
|
||||
} else if (option == GuestAcceptConnection) {
|
||||
int listening = 0;
|
||||
socklen_t size = sizeof(listening);
|
||||
if (::getsockopt(handle, SOL_SOCKET, SO_ACCEPTCONN, reinterpret_cast<NativeOptionOutput>(&listening), &size)) return HostError();
|
||||
number = listening != 0 ? GuestAcceptConnection : 0;
|
||||
} else if (option == GuestType) {
|
||||
number = type;
|
||||
} else if (option == GuestError) {
|
||||
int pending = 0;
|
||||
socklen_t size = sizeof(pending);
|
||||
if (::getsockopt(handle, SOL_SOCKET, SO_ERROR, reinterpret_cast<NativeOptionOutput>(&pending), &size)) return HostError();
|
||||
#ifdef _WIN32
|
||||
if (pending != 0 && pending == state.reportedError) pending = 0;
|
||||
else if (pending != 0) state.reportedError = pending;
|
||||
#endif
|
||||
number = pending == 0 ? 0 : GuestErrorFromHost(pending);
|
||||
} else if (option == GuestSendBuffer || option == GuestReceiveBuffer) {
|
||||
socklen_t size = sizeof(number);
|
||||
if (::getsockopt(handle, SOL_SOCKET, option == GuestSendBuffer ? SO_SNDBUF : SO_RCVBUF, reinterpret_cast<NativeOptionOutput>(&number), &size)) return HostError();
|
||||
} else {
|
||||
return ErrorNoProtocolOption;
|
||||
}
|
||||
return Copy(value, length, &number, sizeof(number));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include "prx/libc/include/HostThreadLocal.hpp"
|
||||
#include "prx/libkernel/Socket/include/SocketError.hpp"
|
||||
#include "prx/libkernel/Socket/include/SocketOptions.hpp"
|
||||
#include "prx/libkernel/Socket/include/SocketRuntime.hpp"
|
||||
#include "prx/libkernel/File/include/DescriptorControl.hpp"
|
||||
#include "prx/libkernel/Equeue/Equeue.hpp"
|
||||
@@ -67,6 +68,12 @@ int NativeError() {
|
||||
return GuestSockets::GuestErrorFromHost(errno);
|
||||
#endif
|
||||
}
|
||||
int ConnectError() {
|
||||
#ifdef _WIN32
|
||||
if (WSAGetLastError() == WSAEWOULDBLOCK) return 36;
|
||||
#endif
|
||||
return NativeError();
|
||||
}
|
||||
int SendError(int type) {
|
||||
#ifdef _WIN32
|
||||
if (type == 2 && WSAGetLastError() == WSAENOTCONN) return 39;
|
||||
@@ -81,6 +88,7 @@ struct Socket {
|
||||
int type;
|
||||
std::mutex modeMutex;
|
||||
bool nonblocking = false;
|
||||
GuestSockets::SocketOptionState options;
|
||||
Socket(NativeSocket value, int family, int type) : value(value), family(family), type(type) {}
|
||||
~Socket() {
|
||||
if (value == Invalid) return;
|
||||
@@ -187,15 +195,6 @@ std::shared_ptr<Socket> Lookup(int descriptor) {
|
||||
Fail(9);
|
||||
return {};
|
||||
}
|
||||
int Option(int guest) {
|
||||
switch (guest) {
|
||||
case 0x4: return SO_REUSEADDR;
|
||||
case 0x20: return SO_BROADCAST;
|
||||
case 0x1001: return SO_SNDBUF;
|
||||
case 0x1002: return SO_RCVBUF;
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
bool Address(const void* input, std::uint32_t length, sockaddr_storage& native, socklen_t& size) {
|
||||
if (!input || length < 2) { Fail(14); return false; }
|
||||
const auto* bytes = static_cast<const unsigned char*>(input);
|
||||
@@ -732,26 +731,20 @@ int APS5_VABI setsockopt_nid_postfix(int descriptor, int level, int option,
|
||||
}
|
||||
const auto socket = Lookup(descriptor);
|
||||
if (!socket) return -1;
|
||||
const int nativeOption = Option(option);
|
||||
if (level != 0xffff || nativeOption == -1) return Fail(42);
|
||||
if (!value || length < sizeof(int)) return Fail(22);
|
||||
return ::setsockopt(socket->value, SOL_SOCKET, nativeOption,
|
||||
static_cast<const char*>(value), sizeof(int)) ? Fail(NativeError()) : 0;
|
||||
if (level != 0xffff) return Fail(42);
|
||||
std::lock_guard lock(socket->modeMutex);
|
||||
const int error = GuestSockets::SetSocketLevelOption(static_cast<std::uintptr_t>(socket->value), socket->options, option, value, length);
|
||||
return error == 0 ? 0 : Fail(error);
|
||||
}
|
||||
int APS5_VABI getsockopt_nid_postfix(int descriptor, int level, int option,
|
||||
void* value, std::uint32_t* length) {
|
||||
const auto socket = Lookup(descriptor);
|
||||
if (!socket) return -1;
|
||||
const int nativeOption = Option(option);
|
||||
if (level != 0xffff || nativeOption == -1) return Fail(42);
|
||||
if (!value || !length || *length < sizeof(int)) return Fail(22);
|
||||
int result = 0;
|
||||
socklen_t size = sizeof(result);
|
||||
if (::getsockopt(socket->value, SOL_SOCKET, nativeOption, reinterpret_cast<char*>(&result), &size))
|
||||
return Fail(NativeError());
|
||||
std::memcpy(value, &result, sizeof(result));
|
||||
*length = sizeof(result);
|
||||
return 0;
|
||||
if (level != 0xffff) return Fail(42);
|
||||
if (!length) return Fail(22);
|
||||
std::lock_guard lock(socket->modeMutex);
|
||||
const int error = GuestSockets::GetSocketLevelOption(static_cast<std::uintptr_t>(socket->value), socket->options, socket->type, option, value, length);
|
||||
return error == 0 ? 0 : Fail(error);
|
||||
}
|
||||
int APS5_VABI socket_nid_postfix(int family, int type, int protocol) {
|
||||
const bool nonblocking = (type & GuestNonBlocking) != 0;
|
||||
@@ -828,7 +821,11 @@ int APS5_VABI connect_nid_postfix(int descriptor, const void* address, std::uint
|
||||
socklen_t size;
|
||||
if (!Address(address, length, native, size)) return -1;
|
||||
if (native.ss_family != (socket->family == 2 ? AF_INET : AF_INET6)) return Fail(47);
|
||||
return ::connect(socket->value, reinterpret_cast<sockaddr*>(&native), size) ? Fail(NativeError()) : 0;
|
||||
{
|
||||
std::lock_guard lock(socket->modeMutex);
|
||||
socket->options.reportedError = 0;
|
||||
}
|
||||
return ::connect(socket->value, reinterpret_cast<sockaddr*>(&native), size) ? Fail(ConnectError()) : 0;
|
||||
}
|
||||
int APS5_VABI listen_nid_postfix(int descriptor, int backlog) {
|
||||
const auto socket = Lookup(descriptor);
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
extern "C" {
|
||||
int APS5_VABI socket_nid_postfix(int, int, int);
|
||||
int APS5_VABI bind_nid_postfix(int, const void*, std::uint32_t);
|
||||
int APS5_VABI listen_nid_postfix(int, int);
|
||||
int APS5_VABI getsockname_nid_postfix(int, void*, std::uint32_t*);
|
||||
int APS5_VABI connect_nid_postfix(int, const void*, std::uint32_t);
|
||||
int APS5_VABI accept_nid_postfix(int, void*, std::uint32_t*);
|
||||
int APS5_VABI setsockopt_nid_postfix(int, int, int, const void*, std::uint32_t);
|
||||
int APS5_VABI getsockopt_nid_postfix(int, int, int, void*, std::uint32_t*);
|
||||
int APS5_VABI fcntl_nid_postfix(int, int, ...);
|
||||
int APS5_VABI poll_nid_postfix(void*, std::uint32_t, int);
|
||||
int APS5_VABI close_nid_postfix(int);
|
||||
int* APS5_VABI __error_nid_postfix();
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int Stream = 1;
|
||||
constexpr int Datagram = 2;
|
||||
constexpr int SolSocket = 0xffff;
|
||||
constexpr int SoAcceptConnection = 0x2;
|
||||
constexpr int SoDebug = 0x1;
|
||||
constexpr int SoKeepAlive = 0x8;
|
||||
constexpr int SoBroadcast = 0x20;
|
||||
constexpr int SoUseLoopback = 0x40;
|
||||
constexpr int SoLinger = 0x80;
|
||||
constexpr int SoReusePort = 0x200;
|
||||
constexpr int SoTimestamp = 0x400;
|
||||
constexpr int SoNoSigpipe = 0x800;
|
||||
constexpr int SoSendBuffer = 0x1001;
|
||||
constexpr int SoError = 0x1007;
|
||||
constexpr int SoType = 0x1008;
|
||||
constexpr int ErrorInvalid = 22;
|
||||
constexpr int ErrorDomain = 33;
|
||||
constexpr int ErrorNoProtocolOption = 42;
|
||||
constexpr int ErrorConnectionRefused = 61;
|
||||
constexpr int ErrorBadDescriptor = 9;
|
||||
|
||||
struct Linger {
|
||||
std::int32_t enabled;
|
||||
std::int32_t seconds;
|
||||
};
|
||||
|
||||
struct PollDescriptor {
|
||||
std::int32_t descriptor;
|
||||
std::int16_t events;
|
||||
std::int16_t revents;
|
||||
};
|
||||
|
||||
void Require(bool condition, const char* what) {
|
||||
if (condition) return;
|
||||
std::fprintf(stderr, "%s (errno %d)\n", what, *__error_nid_postfix());
|
||||
std::abort();
|
||||
}
|
||||
|
||||
int GetInt(int descriptor, int option) {
|
||||
int value = -12345;
|
||||
std::uint32_t size = sizeof(value);
|
||||
Require(getsockopt_nid_postfix(descriptor, SolSocket, option, &value, &size) == 0 && size == sizeof(value), "getsockopt of an integer option failed");
|
||||
return value;
|
||||
}
|
||||
|
||||
void SetInt(int descriptor, int option, int value) {
|
||||
Require(setsockopt_nid_postfix(descriptor, SolSocket, option, &value, sizeof(value)) == 0, "setsockopt of an integer option failed");
|
||||
}
|
||||
|
||||
bool SetFails(int descriptor, int level, int option, const void* value, std::uint32_t length, int error) {
|
||||
return setsockopt_nid_postfix(descriptor, level, option, value, length) == -1 && *__error_nid_postfix() == error;
|
||||
}
|
||||
|
||||
std::array<std::uint8_t, 16> Loopback(std::uint16_t portNetworkOrder) {
|
||||
std::array<std::uint8_t, 16> address{16, 2};
|
||||
std::memcpy(address.data() + 2, &portNetworkOrder, 2);
|
||||
address[4] = 127;
|
||||
address[7] = 1;
|
||||
return address;
|
||||
}
|
||||
|
||||
std::array<std::uint8_t, 16> Listen(int* listener) {
|
||||
*listener = socket_nid_postfix(2, Stream, 0);
|
||||
Require(*listener >= 0, "socket failed");
|
||||
auto address = Loopback(0);
|
||||
Require(bind_nid_postfix(*listener, address.data(), address.size()) == 0, "bind failed");
|
||||
Require(listen_nid_postfix(*listener, 4) == 0, "listen failed");
|
||||
std::uint32_t size = address.size();
|
||||
Require(getsockname_nid_postfix(*listener, address.data(), &size) == 0, "getsockname failed");
|
||||
return address;
|
||||
}
|
||||
|
||||
void WaitUntilSettled(int descriptor) {
|
||||
PollDescriptor entry{descriptor, 0x1 | 0x4, 0};
|
||||
Require(poll_nid_postfix(&entry, 1, 6000) == 1, "the nonblocking connect did not settle within 6 s");
|
||||
}
|
||||
|
||||
void ReportsTypeAndListening() {
|
||||
const int stream = socket_nid_postfix(2, Stream, 0);
|
||||
const int datagram = socket_nid_postfix(2, Datagram, 0);
|
||||
Require(stream >= 0 && datagram >= 0, "socket failed");
|
||||
Require(GetInt(stream, SoType) == Stream, "SO_TYPE of a stream socket must be SOCK_STREAM (1)");
|
||||
Require(GetInt(datagram, SoType) == Datagram, "SO_TYPE of a datagram socket must be SOCK_DGRAM (2)");
|
||||
Require(GetInt(stream, SoAcceptConnection) == 0, "SO_ACCEPTCONN must be 0 before listen");
|
||||
auto address = Loopback(0);
|
||||
Require(bind_nid_postfix(stream, address.data(), address.size()) == 0 && listen_nid_postfix(stream, 4) == 0, "bind or listen failed");
|
||||
Require(GetInt(stream, SoAcceptConnection) == SoAcceptConnection, "SO_ACCEPTCONN of a listener is the option bit (2), as FreeBSD returns so_options & SO_ACCEPTCONN");
|
||||
Require(close_nid_postfix(stream) == 0 && close_nid_postfix(datagram) == 0, "close failed");
|
||||
}
|
||||
|
||||
void ReportsAndClearsTheConnectError() {
|
||||
int listener = -1;
|
||||
auto address = Listen(&listener);
|
||||
const int client = socket_nid_postfix(2, Stream, 0);
|
||||
Require(client >= 0 && fcntl_nid_postfix(client, 4, 4) == 0, "nonblocking socket setup failed");
|
||||
const int result = connect_nid_postfix(client, address.data(), address.size());
|
||||
Require(result == 0 || (result == -1 && *__error_nid_postfix() == 36), "nonblocking connect to a listener must succeed or be in progress");
|
||||
WaitUntilSettled(client);
|
||||
Require(GetInt(client, SoError) == 0, "SO_ERROR after a successful connect must be 0");
|
||||
const int accepted = accept_nid_postfix(listener, nullptr, nullptr);
|
||||
Require(accepted >= 0, "accept failed");
|
||||
for (const int descriptor : {client, accepted, listener}) Require(close_nid_postfix(descriptor) == 0, "close failed");
|
||||
|
||||
int closed = -1;
|
||||
auto refused = Listen(&closed);
|
||||
Require(close_nid_postfix(closed) == 0, "close failed");
|
||||
const int failing = socket_nid_postfix(2, Stream, 0);
|
||||
Require(failing >= 0 && fcntl_nid_postfix(failing, 4, 4) == 0, "nonblocking socket setup failed");
|
||||
const int attempt = connect_nid_postfix(failing, refused.data(), refused.size());
|
||||
if (attempt == -1 && *__error_nid_postfix() != 36) {
|
||||
Require(*__error_nid_postfix() == ErrorConnectionRefused, "a refused connect must fail with ECONNREFUSED (61)");
|
||||
} else {
|
||||
WaitUntilSettled(failing);
|
||||
Require(GetInt(failing, SoError) == ErrorConnectionRefused, "SO_ERROR after a refused connect must be ECONNREFUSED (61)");
|
||||
Require(GetInt(failing, SoError) == 0, "reading SO_ERROR must clear it");
|
||||
}
|
||||
Require(close_nid_postfix(failing) == 0, "close failed");
|
||||
}
|
||||
|
||||
void BooleanOptionsReportTheirBit() {
|
||||
const int stream = socket_nid_postfix(2, Stream, 0);
|
||||
const int datagram = socket_nid_postfix(2, Datagram, 0);
|
||||
Require(stream >= 0 && datagram >= 0, "socket failed");
|
||||
Require(GetInt(stream, SoKeepAlive) == 0, "SO_KEEPALIVE is off by default");
|
||||
SetInt(stream, SoKeepAlive, 1);
|
||||
Require(GetInt(stream, SoKeepAlive) == SoKeepAlive, "an enabled boolean option reads back as its own bit (FreeBSD returns so_options & option)");
|
||||
SetInt(stream, SoKeepAlive, 0);
|
||||
Require(GetInt(stream, SoKeepAlive) == 0, "SO_KEEPALIVE must turn off");
|
||||
SetInt(datagram, SoBroadcast, 1);
|
||||
Require(GetInt(datagram, SoBroadcast) == SoBroadcast, "SO_BROADCAST reads back as its bit");
|
||||
for (const int option : {SoNoSigpipe, SoReusePort, SoDebug, SoUseLoopback, SoTimestamp}) {
|
||||
const int one = 1;
|
||||
Require(SetFails(stream, SolSocket, option, &one, sizeof(one), ErrorNoProtocolOption), "an option no title needs must fail with ENOPROTOOPT (42)");
|
||||
int value = 0;
|
||||
std::uint32_t size = sizeof(value);
|
||||
Require(getsockopt_nid_postfix(stream, SolSocket, option, &value, &size) == -1 && *__error_nid_postfix() == ErrorNoProtocolOption, "getsockopt of an option no title needs must fail with ENOPROTOOPT (42)");
|
||||
}
|
||||
Require(close_nid_postfix(stream) == 0 && close_nid_postfix(datagram) == 0, "close failed");
|
||||
}
|
||||
|
||||
void LingerFollowsFreeBsd() {
|
||||
const int stream = socket_nid_postfix(2, Stream, 0);
|
||||
Require(stream >= 0, "socket failed");
|
||||
Linger linger{1, 5};
|
||||
Require(setsockopt_nid_postfix(stream, SolSocket, SoLinger, &linger, sizeof(linger)) == 0, "SO_LINGER must be accepted");
|
||||
Linger read{};
|
||||
std::uint32_t size = sizeof(read);
|
||||
Require(getsockopt_nid_postfix(stream, SolSocket, SoLinger, &read, &size) == 0 && size == sizeof(read), "getsockopt SO_LINGER failed");
|
||||
Require(read.enabled == SoLinger && read.seconds == 5, "SO_LINGER reads back as {SO_LINGER bit, seconds}");
|
||||
linger = {0, 0};
|
||||
Require(setsockopt_nid_postfix(stream, SolSocket, SoLinger, &linger, sizeof(linger)) == 0, "SO_LINGER off must be accepted");
|
||||
size = sizeof(read);
|
||||
Require(getsockopt_nid_postfix(stream, SolSocket, SoLinger, &read, &size) == 0 && read.enabled == 0, "SO_LINGER must read back as off");
|
||||
linger = {1, -1};
|
||||
Require(SetFails(stream, SolSocket, SoLinger, &linger, sizeof(linger), ErrorDomain), "a negative linger time must fail with EDOM (33)");
|
||||
linger = {1, 65536};
|
||||
Require(SetFails(stream, SolSocket, SoLinger, &linger, sizeof(linger), ErrorDomain), "a linger time above 65535 must fail with EDOM (33)");
|
||||
Require(SetFails(stream, SolSocket, SoLinger, &linger, sizeof(std::int32_t), ErrorInvalid), "a linger value shorter than struct linger must fail with EINVAL (22)");
|
||||
Require(close_nid_postfix(stream) == 0, "close failed");
|
||||
}
|
||||
|
||||
void LengthsFollowFreeBsd() {
|
||||
const int stream = socket_nid_postfix(2, Stream, 0);
|
||||
Require(stream >= 0, "socket failed");
|
||||
const int one = 1;
|
||||
Require(SetFails(stream, SolSocket, SoKeepAlive, &one, 2, ErrorInvalid), "an integer option shorter than an int must fail with EINVAL (22)");
|
||||
const std::array<std::int64_t, 1> wide{1};
|
||||
Require(setsockopt_nid_postfix(stream, SolSocket, SoKeepAlive, wide.data(), sizeof(wide)) == 0, "a value longer than an int is read from its first int");
|
||||
std::array<std::uint8_t, 2> narrow{0xaa, 0xaa};
|
||||
std::uint32_t size = narrow.size();
|
||||
Require(getsockopt_nid_postfix(stream, SolSocket, SoKeepAlive, narrow.data(), &size) == 0 && size == narrow.size(), "a short buffer gets a truncated value and its own length (sooptcopyout)");
|
||||
Require(narrow[0] == SoKeepAlive && narrow[1] == 0, "the truncated value is the low bytes of the int");
|
||||
const int zero = 0;
|
||||
Require(SetFails(stream, SolSocket, SoSendBuffer, &zero, sizeof(zero), ErrorInvalid), "SO_SNDBUF below 1 must fail with EINVAL (22)");
|
||||
const int negative = -5;
|
||||
Require(SetFails(stream, SolSocket, SoSendBuffer, &negative, sizeof(negative), ErrorInvalid), "a negative SO_SNDBUF must fail with EINVAL (22)");
|
||||
SetInt(stream, SoSendBuffer, 65536);
|
||||
Require(close_nid_postfix(stream) == 0, "close failed");
|
||||
}
|
||||
|
||||
void UnknownRequestsFail() {
|
||||
const int stream = socket_nid_postfix(2, Stream, 0);
|
||||
Require(stream >= 0, "socket failed");
|
||||
const int one = 1;
|
||||
Require(SetFails(stream, SolSocket, 0x7fff, &one, sizeof(one), ErrorNoProtocolOption), "an unknown SOL_SOCKET option must fail with ENOPROTOOPT (42)");
|
||||
int value = 0;
|
||||
std::uint32_t size = sizeof(value);
|
||||
Require(getsockopt_nid_postfix(stream, SolSocket, 0x7fff, &value, &size) == -1 && *__error_nid_postfix() == ErrorNoProtocolOption, "getsockopt of an unknown option must fail with ENOPROTOOPT (42)");
|
||||
Require(setsockopt_nid_postfix(-1, SolSocket, SoKeepAlive, &one, sizeof(one)) == -1 && *__error_nid_postfix() == ErrorBadDescriptor, "setsockopt on a bad descriptor must fail with EBADF (9)");
|
||||
Require(close_nid_postfix(stream) == 0, "close failed");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
ReportsTypeAndListening();
|
||||
ReportsAndClearsTheConnectError();
|
||||
BooleanOptionsReportTheirBit();
|
||||
LingerFollowsFreeBsd();
|
||||
LengthsFollowFreeBsd();
|
||||
UnknownRequestsFail();
|
||||
return 0;
|
||||
}
|
||||
@@ -144,6 +144,7 @@
|
||||
- [Socket descriptor numbers](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel, libSceNet) - `socket`, `accept`, `socketpair`, `dup`, `sceNetSocket` and `sceNetAccept` take the lowest free number of the host descriptor table that files use, as FreeBSD 11.4 `fdalloc` takes the lowest free entry of the one process table (`kern_descrip.c`), so every number stays below `FD_SETSIZE` (1024) while fewer than 1024 descriptors are open. The number holds a zero-access `NUL` handle on Windows and a duplicate of the host socket on Linux, and the socket tables map it to the socket. The host table also holds the runtime's own files, so the numbers differ from the console's; the first number `socket` and `sceNetSocket` return on a console and its `getdtablesize` are not measured. Paths that bypass the socket tables see the placeholder: libc `fdopen` on a socket opens it, libkernel `fstat` on a socket throws, and libkernel `close` of a libSceNet socket releases the number but leaves the libSceNet socket open. Palworld (PPSA23126), The Mound (PPSA26223) and Dying Light 2 (PPSA02262) set socket bits in a 1024-bit `fd_set`
|
||||
- [poll](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) waits on guest sockets and pipes, through `WSAPoll` on Windows and `poll` on Linux; the `pollfd` layout and flag values are FreeBSD's. `WSAPoll` rejects `POLLPRI`, so on Windows a socket's `POLLPRI` is asked as `POLLRDBAND` and `POLLIN` as `POLLRDNORM`; Winsock reports urgent data until it is read with `MSG_OOB`, where FreeBSD 11.4's `sopoll_generic` stops once the normal stream passes the mark. Guest `send` and `recv` refuse `MSG_OOB` with `EOPNOTSUPP`, so urgent data can only come from an outside peer and Windows keeps reporting `POLLPRI` after the stream passes the mark. A pipe follows FreeBSD's `pipe_poll`: the read end is readable when data is buffered, either end reports `POLLHUP` once the other end is closed, with the requested `POLLIN` and `POLLRDNORM` bits and without `POLLOUT`, which `pollscan` clears, and the write end is writable when `PIPE_BUF` (512) bytes are free on Windows, or when the host kernel says so on Linux. `POLLOUT` on a read end throws: FreeBSD opens both ends for reading and writing and reports it writable, and host pipes are one-way. Windows pipes cannot be waited on, so while a pipe is polled `poll` checks it with `NtQueryInformationFile` and waits on the sockets in slices of 1 ms. A closed descriptor reports `POLLNVAL`, and an open descriptor that is neither a guest socket nor a pipe (files, stdio, libSceNet sockets) throws, as do event bits other than `POLLIN`, `POLLPRI`, `POLLOUT`, `POLLRDNORM`, `POLLRDBAND` and `POLLWRBAND` (the `POLLERR`, `POLLHUP` and `POLLNVAL` bits are ignored in `events`), an infinite timeout with no socket or pipe to wait on. A closed socket descriptor reports `POLLNVAL`; the console's limit on the descriptor count is unknown and not checked
|
||||
- [Socket error numbers](../../../core/libs/prx/libkernel/Socket/include/SocketError.hpp) (libkernel) - a host socket error becomes the FreeBSD 11 `errno.h` number of the same name; the numbers are not checked on the console. Winsock `WSAESHUTDOWN` becomes `EPIPE`, as a send after `shutdown` does on FreeBSD, and a host error with no FreeBSD socket counterpart becomes `EIO`. A nonblocking `connect` in progress reports `EINPROGRESS` on Linux but `EAGAIN` on Windows (`WSAEWOULDBLOCK`), where FreeBSD reports `EINPROGRESS`. On Windows a refused nonblocking `connect` is never reported (`SO_ERROR` is unsupported), so a repeated one keeps reporting `EALREADY` where FreeBSD reports `ECONNREFUSED`. A receive after `shutdown(SHUT_RD)` reports `EPIPE` on Windows where FreeBSD returns 0
|
||||
- [getsockopt, setsockopt](../../../core/libs/prx/libkernel/Socket/src/SocketOptions.cpp) (libkernel) on a guest socket handle the SOL_SOCKET level only, after FreeBSD 11.4's [`sosetopt` and `sogetopt`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/uipc_socket.c): `SO_REUSEADDR`, `SO_KEEPALIVE`, `SO_DONTROUTE`, `SO_BROADCAST`, `SO_OOBINLINE` and `SO_LINGER` act on the host socket, and `SO_ERROR`, `SO_TYPE`, `SO_ACCEPTCONN`, `SO_SNDBUF` and `SO_RCVBUF` are read. A boolean option reads back as its own bit, as FreeBSD returns `so_options & option`. Winsock does not clear a socket's pending error when it is read, so on Windows the library reports a pending error once and shows it again only after a new `connect`; a second failure with the same code on the same socket without a `connect` in between would be hidden. `SO_SNDBUF` and `SO_RCVBUF` read the host's value, which is not the size FreeBSD reports (Linux doubles it). Every other SOL_SOCKET option (`SO_DEBUG`, `SO_USELOOPBACK`, `SO_REUSEPORT`, `SO_TIMESTAMP` and `SO_NOSIGPIPE`, which FreeBSD stores and no title has been seen to set, the send and receive timeouts and low-water marks among them) and every other level (`IPPROTO_TCP`, `IPPROTO_IP`, `IPPROTO_IPV6`) fails with `ENOPROTOOPT`. None of this was measured on a console.
|
||||
- [connect to AF_UNSPEC](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) dissolves a datagram association as FreeBSD 11.4's `udp_disconnect()` does, which resets the local address to the wildcard address and keeps the port. On Linux a socket bound to an explicit port keeps its local address as well (`127.0.0.1:<port>` stays `127.0.0.1:<port>`), because a bound socket cannot be bound again; one bound to port 0 is bound again to the wildcard address with the port it had
|
||||
- [msync](../../../core/libs/prx/libkernel/DirectMemory/PosixMemory.cpp) (libkernel) follows FreeBSD 11's `kern_msync` and `vm_map_sync`, not measured on the console: the address is rounded down to the page, a range that wraps is `EINVAL`, as is `MS_ASYNC` with `MS_INVALIDATE`, and a range whose start or any page is not mapped (as `sceKernelVirtualQuery` reports it, reservations included) is `ENOMEM`; a page unmapped from inside a larger mapping still counts as mapped there, since `sceKernelVirtualQuery` keeps reporting the whole mapping. The guest has no file mappings, so a successful call writes nothing back. `MS_INVALIDATE` on a mapped range throws: FreeBSD returns `EBUSY` when the range holds `mlock`ed pages, and locked pages are not tracked
|
||||
- [madvise](../../../core/libs/prx/libkernel/DirectMemory/PosixMemory.cpp) (libkernel) accepts `MADV_PROTECT` (10) as a no-op hint without modifying process protection. FreeBSD 11's `kern_madvise` (`sys/vm/vm_mmap.c`) handles it before the address check, ignores the address and length, and calls `kern_procctl(..., PROC_SPROTECT, PPROT_SET)`, which requires `PRIV_VM_MADV_PROTECT` and fails with `EPERM` without it. Returning 0 unconditionally assumes the calling process is privileged. Console behaviour is unverified
|
||||
|
||||
Reference in new issue
Block a user