fix(libSceNet): report SO_ERROR from sceNetGetsockopt
This commit is contained in:
parent
c2565f0149
commit
0daf1d5ba3
|
|
@ -82,6 +82,7 @@ constexpr int NET_EPOLL_CTL_DEL = 3;
|
|||
constexpr int NET_SOL_SOCKET = 0xFFFF;
|
||||
constexpr int NET_SO_SNDTIMEO = 0x1005;
|
||||
constexpr int NET_SO_RCVTIMEO = 0x1006;
|
||||
constexpr int NET_SO_ERROR = 0x1007;
|
||||
constexpr int NET_SO_NBIO = 0x1200;
|
||||
constexpr int NET_SO_REUSEADDR = 0x4;
|
||||
constexpr int NET_SO_BROADCAST = 0x20;
|
||||
|
|
@ -104,8 +105,8 @@ bool initialize_sockets() {
|
|||
return result == 0;
|
||||
}
|
||||
void close_socket(NativeSocket socket) { closesocket(socket); }
|
||||
int native_error() {
|
||||
switch (WSAGetLastError()) {
|
||||
int net_error(int code) {
|
||||
switch (code) {
|
||||
case WSAEWOULDBLOCK: return NET_EAGAIN;
|
||||
case WSAEINPROGRESS: return NET_EINPROGRESS;
|
||||
case WSAEALREADY: return NET_EALREADY;
|
||||
|
|
@ -130,6 +131,7 @@ int native_error() {
|
|||
default: return 5;
|
||||
}
|
||||
}
|
||||
int native_error() { return net_error(WSAGetLastError()); }
|
||||
#else
|
||||
using NativeSocket = int;
|
||||
using NativeLength = socklen_t;
|
||||
|
|
@ -137,8 +139,8 @@ constexpr NativeSocket INVALID_NATIVE_SOCKET = -1;
|
|||
constexpr int NATIVE_SEND_FLAGS = MSG_NOSIGNAL;
|
||||
bool initialize_sockets() { return true; }
|
||||
void close_socket(NativeSocket socket) { ::close(socket); }
|
||||
int native_error() {
|
||||
switch (errno) {
|
||||
int net_error(int code) {
|
||||
switch (code) {
|
||||
case EAGAIN: return NET_EAGAIN;
|
||||
#if EWOULDBLOCK != EAGAIN
|
||||
case EWOULDBLOCK: return NET_EAGAIN;
|
||||
|
|
@ -166,6 +168,7 @@ int native_error() {
|
|||
default: return 5;
|
||||
}
|
||||
}
|
||||
int native_error() { return net_error(errno); }
|
||||
#endif
|
||||
|
||||
struct NativeSocketHandle {
|
||||
|
|
@ -241,6 +244,7 @@ struct Sock {
|
|||
bool aborted = false;
|
||||
int rcv_timeout_us = 0;
|
||||
int snd_timeout_us = 0;
|
||||
int reported_error = 0;
|
||||
};
|
||||
|
||||
#ifdef _WIN32
|
||||
|
|
@ -756,6 +760,7 @@ int APS5_VABI sceNetConnect(int s, const void* addr, uint32_t addrlen) {
|
|||
std::lock_guard lk(g_mutex);
|
||||
auto it = g_socks.find(s);
|
||||
if (it == g_socks.end()) return fail(NET_EBADF);
|
||||
it->second.reported_error = 0;
|
||||
socket = it->second;
|
||||
}
|
||||
sockaddr_storage native{};
|
||||
|
|
@ -992,6 +997,21 @@ int APS5_VABI sceNetGetsockopt(int s, int level, int optname, void* optval, uint
|
|||
case NET_SO_NBIO: value = socket.nonblock ? 1 : 0; break;
|
||||
case NET_SO_RCVTIMEO: value = socket.rcv_timeout_us; break;
|
||||
case NET_SO_SNDTIMEO: value = socket.snd_timeout_us; break;
|
||||
case NET_SO_ERROR: {
|
||||
int pending = 0;
|
||||
NativeLength size = sizeof(pending);
|
||||
if (::getsockopt(socket.native->value, SOL_SOCKET, SO_ERROR, reinterpret_cast<char*>(&pending), &size) != 0)
|
||||
return fail(native_error());
|
||||
#ifdef _WIN32
|
||||
std::lock_guard lk(g_mutex);
|
||||
auto it = g_socks.find(s);
|
||||
if (it == g_socks.end()) return fail(NET_EBADF);
|
||||
if (pending != 0 && pending == it->second.reported_error) pending = 0;
|
||||
else if (pending != 0) it->second.reported_error = pending;
|
||||
#endif
|
||||
value = pending == 0 ? 0 : net_error(pending);
|
||||
break;
|
||||
}
|
||||
default: return fail(NET_EOPNOTSUPP);
|
||||
}
|
||||
std::memcpy(optval, &value, sizeof(value));
|
||||
|
|
|
|||
|
|
@ -463,6 +463,36 @@ static void CheckIpv6SocketsAreIpv6Only() {
|
|||
Require(sceNetSocketClose(inet) == 0);
|
||||
}
|
||||
|
||||
static void CheckSocketError() {
|
||||
std::array<std::uint8_t, 16> address{16, 2, 0, 0, 127, 0, 0, 1};
|
||||
const int listener = sceNetSocket(nullptr, 2, 1, 6);
|
||||
Require(listener >= 0 && sceNetBind_nid_postfix(listener, address.data(), address.size()) == 0 && sceNetListen(listener, 1) == 0);
|
||||
std::uint32_t size = address.size();
|
||||
Require(sceNetGetsockname(listener, address.data(), &size) == 0 && size == address.size());
|
||||
const int connected = sceNetSocket(nullptr, 2, 1, 6);
|
||||
Require(connected >= 0 && sceNetConnect(connected, address.data(), address.size()) == 0);
|
||||
int error = -1;
|
||||
size = sizeof(error);
|
||||
Require(sceNetGetsockopt(connected, 0xffff, 0x1007, &error, &size) == 0 && size == sizeof(error) && error == 0);
|
||||
for (const int descriptor : {connected, listener}) Require(sceNetSocketClose(descriptor) == 0);
|
||||
|
||||
const int refused = sceNetSocket(nullptr, 2, 1, 6);
|
||||
const int nonblocking = 1;
|
||||
Require(refused >= 0 && sceNetSetsockopt(refused, 0xffff, 0x1200, &nonblocking, sizeof(nonblocking)) == 0);
|
||||
const int started = sceNetConnect(refused, address.data(), address.size());
|
||||
Require(started == static_cast<int>(0x80410124) && *sceNetErrnoLoc() == 36);
|
||||
error = 0;
|
||||
for (int attempt = 0; attempt < 500 && error == 0; ++attempt) {
|
||||
size = sizeof(error);
|
||||
Require(sceNetGetsockopt(refused, 0xffff, 0x1007, &error, &size) == 0 && size == sizeof(error));
|
||||
if (error == 0) std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
}
|
||||
Require(error == 61);
|
||||
size = sizeof(error);
|
||||
Require(sceNetGetsockopt(refused, 0xffff, 0x1007, &error, &size) == 0 && error == 0);
|
||||
Require(sceNetSocketClose(refused) == 0);
|
||||
}
|
||||
|
||||
int main() {
|
||||
const auto epoch = std::chrono::steady_clock::time_point{};
|
||||
Require(KernelSocketSelect::DeadlineAfter(epoch, 2, 250000) == epoch + std::chrono::milliseconds(2250));
|
||||
|
|
@ -495,6 +525,7 @@ int main() {
|
|||
Require(close_nid_postfix(closed) == 0);
|
||||
Require(Failed(sceNetSocketClose(closed), 9));
|
||||
CheckKernelTimeouts();
|
||||
CheckSocketError();
|
||||
|
||||
const int listener = sceNetSocket(nullptr, 2, 1, 6);
|
||||
Require(listener >= 0);
|
||||
|
|
|
|||
|
|
@ -9,5 +9,6 @@
|
|||
### Functional
|
||||
|
||||
- [sceNetSetsockopt](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) sets `SO_RCVTIMEO` and `SO_SNDTIMEO` as the host socket's timeouts. On Windows a receive or send on a stream socket that times out fails with `ETIMEDOUT`, where FreeBSD returns `EAGAIN` or the partial count: Winsock leaves the connection in an indeterminate state after a timeout and reports no partial count ([SOL_SOCKET options](https://learn.microsoft.com/en-us/windows/win32/winsock/sol-socket-socket-options)), so FreeBSD's behaviour needs the timeout waited for without the host option. A receive on a datagram socket that times out fails with `EAGAIN`
|
||||
- [sceNetGetsockopt](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) reads `SO_ERROR` from the host socket. 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 `sceNetConnect`; a second failure with the same code on the same socket without a `sceNetConnect` in between would be hidden.
|
||||
- [sceNetSendmsg and sceNetRecvmsg](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) copy the scatter/gather list through one host buffer and accept only the flags `sceNetSend`/`sceNetRecv` accept. Sending control data throws; a receive never returns control data (`msg_controllen` 0), since no socket option that queues it is supported
|
||||
- [sceNetResolverAbort and sceNetResolverStartNtoaMultipleRecords](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) follow shadPS4 ([net.cpp](https://github.com/shadps4-emu/shadPS4/blob/811bfcd7d17ed8ae975c477d9ff28abc744e1ca8/src/core/libraries/net/net.cpp), [net_resolver.cpp](https://github.com/shadps4-emu/shadPS4/blob/811bfcd7d17ed8ae975c477d9ff28abc744e1ca8/src/core/libraries/net/net_resolver.cpp)); the `(int rid, int flags)` signature of the abort also matches [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Net/Socket.cs). Unverified on hardware. Lookups run synchronously over the host resolver, which cannot be interrupted: an abort while a lookup runs on another thread returns at once, and that lookup fails with `EINTR` without writing its output only when the host call returns; the abort flags are then ignored, as shadPS4 does for a running lookup. With nothing running, abort flag 1 (NTOA preservation) or 2 (ATON preservation) makes the next `sceNetResolverStartNtoa*` or `sceNetResolverStartAton` call on that resolver fail with `EINTR`, reported by `sceNetResolverGetError`, after its arguments are checked. Unlike shadPS4, which ignores them, other abort flag bits throw, and `sceNetResolverStartNtoaMultipleRecords` throws for any flag, including async (1) and 0x10000 (IP address literals disabled), as `sceNetResolverStartNtoaMultipleRecordsEx` does
|
||||
|
|
|
|||
Loading…
Reference in New Issue