Improve behavior when sending to closed connection

- On Unix, this would previously kill the Yuzu process with SIGPIPE.
  Send MSG_NOSIGNAL to opt out of this.

- Add support for the proper error code in this situation, EPIPE.

- Windows has nonstandard behavior in this situation; translate it to
  the standard behavior.  Kind of pointless, but isn't it nice to be
  correct?
This commit is contained in:
comex 2023-07-01 17:28:43 -07:00 committed by Liam
parent a8c4f01f6c
commit 755bcc459b
4 changed files with 36 additions and 7 deletions

View file

@ -18,7 +18,9 @@ enum class Errno : u32 {
AGAIN = 11, AGAIN = 11,
INVAL = 22, INVAL = 22,
MFILE = 24, MFILE = 24,
PIPE = 32,
MSGSIZE = 90, MSGSIZE = 90,
CONNABORTED = 103,
CONNRESET = 104, CONNRESET = 104,
NOTCONN = 107, NOTCONN = 107,
TIMEDOUT = 110, TIMEDOUT = 110,

View file

@ -23,10 +23,14 @@ Errno Translate(Network::Errno value) {
return Errno::INVAL; return Errno::INVAL;
case Network::Errno::MFILE: case Network::Errno::MFILE:
return Errno::MFILE; return Errno::MFILE;
case Network::Errno::PIPE:
return Errno::PIPE;
case Network::Errno::NOTCONN: case Network::Errno::NOTCONN:
return Errno::NOTCONN; return Errno::NOTCONN;
case Network::Errno::TIMEDOUT: case Network::Errno::TIMEDOUT:
return Errno::TIMEDOUT; return Errno::TIMEDOUT;
case Network::Errno::CONNABORTED:
return Errno::CONNABORTED;
case Network::Errno::CONNRESET: case Network::Errno::CONNRESET:
return Errno::CONNRESET; return Errno::CONNRESET;
case Network::Errno::INPROGRESS: case Network::Errno::INPROGRESS:

View file

@ -39,6 +39,11 @@ namespace Network {
namespace { namespace {
enum class CallType {
Send,
Other,
};
#ifdef _WIN32 #ifdef _WIN32
using socklen_t = int; using socklen_t = int;
@ -96,7 +101,7 @@ bool EnableNonBlock(SOCKET fd, bool enable) {
return ioctlsocket(fd, FIONBIO, &value) != SOCKET_ERROR; return ioctlsocket(fd, FIONBIO, &value) != SOCKET_ERROR;
} }
Errno TranslateNativeError(int e) { Errno TranslateNativeError(int e, CallType call_type = CallType::Other) {
switch (e) { switch (e) {
case 0: case 0:
return Errno::SUCCESS; return Errno::SUCCESS;
@ -112,6 +117,14 @@ Errno TranslateNativeError(int e) {
return Errno::AGAIN; return Errno::AGAIN;
case WSAECONNREFUSED: case WSAECONNREFUSED:
return Errno::CONNREFUSED; return Errno::CONNREFUSED;
case WSAECONNABORTED:
if (call_type == CallType::Send) {
// Winsock yields WSAECONNABORTED from `send` in situations where Unix
// systems, and actual Switches, yield EPIPE.
return Errno::PIPE;
} else {
return Errno::CONNABORTED;
}
case WSAECONNRESET: case WSAECONNRESET:
return Errno::CONNRESET; return Errno::CONNRESET;
case WSAEHOSTUNREACH: case WSAEHOSTUNREACH:
@ -198,7 +211,7 @@ bool EnableNonBlock(int fd, bool enable) {
return fcntl(fd, F_SETFL, flags) == 0; return fcntl(fd, F_SETFL, flags) == 0;
} }
Errno TranslateNativeError(int e) { Errno TranslateNativeError(int e, CallType call_type = CallType::Other) {
switch (e) { switch (e) {
case 0: case 0:
return Errno::SUCCESS; return Errno::SUCCESS;
@ -208,6 +221,10 @@ Errno TranslateNativeError(int e) {
return Errno::INVAL; return Errno::INVAL;
case EMFILE: case EMFILE:
return Errno::MFILE; return Errno::MFILE;
case EPIPE:
return Errno::PIPE;
case ECONNABORTED:
return Errno::CONNABORTED;
case ENOTCONN: case ENOTCONN:
return Errno::NOTCONN; return Errno::NOTCONN;
case EAGAIN: case EAGAIN:
@ -236,13 +253,13 @@ Errno TranslateNativeError(int e) {
#endif #endif
Errno GetAndLogLastError() { Errno GetAndLogLastError(CallType call_type = CallType::Other) {
#ifdef _WIN32 #ifdef _WIN32
int e = WSAGetLastError(); int e = WSAGetLastError();
#else #else
int e = errno; int e = errno;
#endif #endif
const Errno err = TranslateNativeError(e); const Errno err = TranslateNativeError(e, call_type);
if (err == Errno::AGAIN || err == Errno::TIMEDOUT || err == Errno::INPROGRESS) { if (err == Errno::AGAIN || err == Errno::TIMEDOUT || err == Errno::INPROGRESS) {
// These happen during normal operation, so only log them at debug level. // These happen during normal operation, so only log them at debug level.
LOG_DEBUG(Network, "Socket operation error: {}", Common::NativeErrorToString(e)); LOG_DEBUG(Network, "Socket operation error: {}", Common::NativeErrorToString(e));
@ -731,13 +748,17 @@ std::pair<s32, Errno> Socket::Send(std::span<const u8> message, int flags) {
ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max())); ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max()));
ASSERT(flags == 0); ASSERT(flags == 0);
int native_flags = 0;
#if YUZU_UNIX
native_flags |= MSG_NOSIGNAL; // do not send us SIGPIPE
#endif
const auto result = send(fd, reinterpret_cast<const char*>(message.data()), const auto result = send(fd, reinterpret_cast<const char*>(message.data()),
static_cast<int>(message.size()), 0); static_cast<int>(message.size()), native_flags);
if (result != SOCKET_ERROR) { if (result != SOCKET_ERROR) {
return {static_cast<s32>(result), Errno::SUCCESS}; return {static_cast<s32>(result), Errno::SUCCESS};
} }
return {-1, GetAndLogLastError()}; return {-1, GetAndLogLastError(CallType::Send)};
} }
std::pair<s32, Errno> Socket::SendTo(u32 flags, std::span<const u8> message, std::pair<s32, Errno> Socket::SendTo(u32 flags, std::span<const u8> message,
@ -759,7 +780,7 @@ std::pair<s32, Errno> Socket::SendTo(u32 flags, std::span<const u8> message,
return {static_cast<s32>(result), Errno::SUCCESS}; return {static_cast<s32>(result), Errno::SUCCESS};
} }
return {-1, GetAndLogLastError()}; return {-1, GetAndLogLastError(CallType::Send)};
} }
Errno Socket::Close() { Errno Socket::Close() {

View file

@ -33,10 +33,12 @@ enum class Errno {
BADF, BADF,
INVAL, INVAL,
MFILE, MFILE,
PIPE,
NOTCONN, NOTCONN,
AGAIN, AGAIN,
CONNREFUSED, CONNREFUSED,
CONNRESET, CONNRESET,
CONNABORTED,
HOSTUNREACH, HOSTUNREACH,
NETDOWN, NETDOWN,
NETUNREACH, NETUNREACH,