Merge pull request #5815 from comex/net-error-reform
Network error handling reform
This commit is contained in:
		| @@ -52,9 +52,13 @@ __declspec(dllimport) void __stdcall DebugBreak(void); | ||||
| // Generic function to get last error message. | ||||
| // Call directly after the command or use the error num. | ||||
| // This function might change the error code. | ||||
| // Defined in Misc.cpp. | ||||
| // Defined in misc.cpp. | ||||
| [[nodiscard]] std::string GetLastErrorMsg(); | ||||
|  | ||||
| // Like GetLastErrorMsg(), but passing an explicit error code. | ||||
| // Defined in misc.cpp. | ||||
| [[nodiscard]] std::string NativeErrorToString(int e); | ||||
|  | ||||
| #define DECLARE_ENUM_FLAG_OPERATORS(type)                                                          \ | ||||
|     [[nodiscard]] constexpr type operator|(type a, type b) noexcept {                              \ | ||||
|         using T = std::underlying_type_t<type>;                                                    \ | ||||
|   | ||||
| @@ -12,27 +12,41 @@ | ||||
|  | ||||
| #include "common/common_funcs.h" | ||||
|  | ||||
| // Generic function to get last error message. | ||||
| // Call directly after the command or use the error num. | ||||
| // This function might change the error code. | ||||
| std::string GetLastErrorMsg() { | ||||
|     static constexpr std::size_t buff_size = 255; | ||||
|     char err_str[buff_size]; | ||||
|  | ||||
| std::string NativeErrorToString(int e) { | ||||
| #ifdef _WIN32 | ||||
|     FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, nullptr, GetLastError(), | ||||
|                    MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), err_str, buff_size, nullptr); | ||||
|     return std::string(err_str, buff_size); | ||||
| #elif defined(__GLIBC__) && (_GNU_SOURCE || (_POSIX_C_SOURCE < 200112L && _XOPEN_SOURCE < 600)) | ||||
|     LPSTR err_str; | ||||
|  | ||||
|     DWORD res = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | | ||||
|                                    FORMAT_MESSAGE_IGNORE_INSERTS, | ||||
|                                nullptr, e, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), | ||||
|                                reinterpret_cast<LPSTR>(&err_str), 1, nullptr); | ||||
|     if (!res) { | ||||
|         return "(FormatMessageA failed to format error)"; | ||||
|     } | ||||
|     std::string ret(err_str); | ||||
|     LocalFree(err_str); | ||||
|     return ret; | ||||
| #else | ||||
|     char err_str[255]; | ||||
| #if defined(__GLIBC__) && (_GNU_SOURCE || (_POSIX_C_SOURCE < 200112L && _XOPEN_SOURCE < 600)) | ||||
|     // Thread safe (GNU-specific) | ||||
|     const char* str = strerror_r(errno, err_str, buff_size); | ||||
|     const char* str = strerror_r(e, err_str, sizeof(err_str)); | ||||
|     return std::string(str); | ||||
| #else | ||||
|     // Thread safe (XSI-compliant) | ||||
|     const int success = strerror_r(errno, err_str, buff_size); | ||||
|     if (success != 0) { | ||||
|         return {}; | ||||
|     int second_err = strerror_r(e, err_str, sizeof(err_str)); | ||||
|     if (second_err != 0) { | ||||
|         return "(strerror_r failed to format error)"; | ||||
|     } | ||||
|     return std::string(err_str); | ||||
| #endif // GLIBC etc. | ||||
| #endif // _WIN32 | ||||
| } | ||||
|  | ||||
| std::string GetLastErrorMsg() { | ||||
| #ifdef _WIN32 | ||||
|     return NativeErrorToString(GetLastError()); | ||||
| #else | ||||
|     return NativeErrorToString(errno); | ||||
| #endif | ||||
| } | ||||
|   | ||||
| @@ -7,6 +7,7 @@ | ||||
| #include <limits> | ||||
| #include <utility> | ||||
| #include <vector> | ||||
| #include "common/common_funcs.h" | ||||
|  | ||||
| #ifdef _WIN32 | ||||
| #define _WINSOCK_DEPRECATED_NO_WARNINGS // gethostname | ||||
| @@ -90,15 +91,36 @@ LINGER MakeLinger(bool enable, u32 linger_value) { | ||||
|     return value; | ||||
| } | ||||
|  | ||||
| int LastError() { | ||||
|     return WSAGetLastError(); | ||||
| } | ||||
|  | ||||
| bool EnableNonBlock(SOCKET fd, bool enable) { | ||||
|     u_long value = enable ? 1 : 0; | ||||
|     return ioctlsocket(fd, FIONBIO, &value) != SOCKET_ERROR; | ||||
| } | ||||
|  | ||||
| Errno TranslateNativeError(int e) { | ||||
|     switch (e) { | ||||
|     case WSAEBADF: | ||||
|         return Errno::BADF; | ||||
|     case WSAEINVAL: | ||||
|         return Errno::INVAL; | ||||
|     case WSAEMFILE: | ||||
|         return Errno::MFILE; | ||||
|     case WSAENOTCONN: | ||||
|         return Errno::NOTCONN; | ||||
|     case WSAEWOULDBLOCK: | ||||
|         return Errno::AGAIN; | ||||
|     case WSAECONNREFUSED: | ||||
|         return Errno::CONNREFUSED; | ||||
|     case WSAEHOSTUNREACH: | ||||
|         return Errno::HOSTUNREACH; | ||||
|     case WSAENETDOWN: | ||||
|         return Errno::NETDOWN; | ||||
|     case WSAENETUNREACH: | ||||
|         return Errno::NETUNREACH; | ||||
|     default: | ||||
|         return Errno::OTHER; | ||||
|     } | ||||
| } | ||||
|  | ||||
| #elif YUZU_UNIX // ^ _WIN32 v YUZU_UNIX | ||||
|  | ||||
| using SOCKET = int; | ||||
| @@ -108,9 +130,6 @@ using ULONG = u64; | ||||
| constexpr SOCKET INVALID_SOCKET = -1; | ||||
| constexpr SOCKET SOCKET_ERROR = -1; | ||||
|  | ||||
| constexpr int WSAEWOULDBLOCK = EAGAIN; | ||||
| constexpr int WSAENOTCONN = ENOTCONN; | ||||
|  | ||||
| constexpr int SD_RECEIVE = SHUT_RD; | ||||
| constexpr int SD_SEND = SHUT_WR; | ||||
| constexpr int SD_BOTH = SHUT_RDWR; | ||||
| @@ -162,10 +181,6 @@ linger MakeLinger(bool enable, u32 linger_value) { | ||||
|     return value; | ||||
| } | ||||
|  | ||||
| int LastError() { | ||||
|     return errno; | ||||
| } | ||||
|  | ||||
| bool EnableNonBlock(int fd, bool enable) { | ||||
|     int flags = fcntl(fd, F_GETFD); | ||||
|     if (flags == -1) { | ||||
| @@ -179,8 +194,43 @@ bool EnableNonBlock(int fd, bool enable) { | ||||
|     return fcntl(fd, F_SETFD, flags) == 0; | ||||
| } | ||||
|  | ||||
| Errno TranslateNativeError(int e) { | ||||
|     switch (e) { | ||||
|     case EBADF: | ||||
|         return Errno::BADF; | ||||
|     case EINVAL: | ||||
|         return Errno::INVAL; | ||||
|     case EMFILE: | ||||
|         return Errno::MFILE; | ||||
|     case ENOTCONN: | ||||
|         return Errno::NOTCONN; | ||||
|     case EAGAIN: | ||||
|         return Errno::AGAIN; | ||||
|     case ECONNREFUSED: | ||||
|         return Errno::CONNREFUSED; | ||||
|     case EHOSTUNREACH: | ||||
|         return Errno::HOSTUNREACH; | ||||
|     case ENETDOWN: | ||||
|         return Errno::NETDOWN; | ||||
|     case ENETUNREACH: | ||||
|         return Errno::NETUNREACH; | ||||
|     default: | ||||
|         return Errno::OTHER; | ||||
|     } | ||||
| } | ||||
|  | ||||
| #endif | ||||
|  | ||||
| Errno GetAndLogLastError() { | ||||
| #ifdef _WIN32 | ||||
|     int e = WSAGetLastError(); | ||||
| #else | ||||
|     int e = errno; | ||||
| #endif | ||||
|     LOG_ERROR(Network, "Socket operation error: {}", NativeErrorToString(e)); | ||||
|     return TranslateNativeError(e); | ||||
| } | ||||
|  | ||||
| int TranslateDomain(Domain domain) { | ||||
|     switch (domain) { | ||||
|     case Domain::INET: | ||||
| @@ -290,9 +340,7 @@ Errno SetSockOpt(SOCKET fd, int option, T value) { | ||||
|     if (result != SOCKET_ERROR) { | ||||
|         return Errno::SUCCESS; | ||||
|     } | ||||
|     const int ec = LastError(); | ||||
|     UNREACHABLE_MSG("Unhandled host socket error={}", ec); | ||||
|     return Errno::SUCCESS; | ||||
|     return GetAndLogLastError(); | ||||
| } | ||||
|  | ||||
| } // Anonymous namespace | ||||
| @@ -308,14 +356,12 @@ NetworkInstance::~NetworkInstance() { | ||||
| std::pair<IPv4Address, Errno> GetHostIPv4Address() { | ||||
|     std::array<char, 256> name{}; | ||||
|     if (gethostname(name.data(), static_cast<int>(name.size()) - 1) == SOCKET_ERROR) { | ||||
|         UNIMPLEMENTED_MSG("Unhandled gethostname error"); | ||||
|         return {IPv4Address{}, Errno::SUCCESS}; | ||||
|         return {IPv4Address{}, GetAndLogLastError()}; | ||||
|     } | ||||
|  | ||||
|     hostent* const ent = gethostbyname(name.data()); | ||||
|     if (!ent) { | ||||
|         UNIMPLEMENTED_MSG("Unhandled gethostbyname error"); | ||||
|         return {IPv4Address{}, Errno::SUCCESS}; | ||||
|         return {IPv4Address{}, GetAndLogLastError()}; | ||||
|     } | ||||
|     if (ent->h_addr_list == nullptr) { | ||||
|         UNIMPLEMENTED_MSG("No addr provided in hostent->h_addr_list"); | ||||
| @@ -359,9 +405,7 @@ std::pair<s32, Errno> Poll(std::vector<PollFD>& pollfds, s32 timeout) { | ||||
|  | ||||
|     ASSERT(result == SOCKET_ERROR); | ||||
|  | ||||
|     const int ec = LastError(); | ||||
|     UNREACHABLE_MSG("Unhandled host socket error={}", ec); | ||||
|     return {-1, Errno::SUCCESS}; | ||||
|     return {-1, GetAndLogLastError()}; | ||||
| } | ||||
|  | ||||
| Socket::~Socket() { | ||||
| @@ -380,9 +424,7 @@ Errno Socket::Initialize(Domain domain, Type type, Protocol protocol) { | ||||
|         return Errno::SUCCESS; | ||||
|     } | ||||
|  | ||||
|     const int ec = LastError(); | ||||
|     UNREACHABLE_MSG("Unhandled host socket error={}", ec); | ||||
|     return Errno::SUCCESS; | ||||
|     return GetAndLogLastError(); | ||||
| } | ||||
|  | ||||
| std::pair<Socket::AcceptResult, Errno> Socket::Accept() { | ||||
| @@ -391,9 +433,7 @@ std::pair<Socket::AcceptResult, Errno> Socket::Accept() { | ||||
|     const SOCKET new_socket = accept(fd, &addr, &addrlen); | ||||
|  | ||||
|     if (new_socket == INVALID_SOCKET) { | ||||
|         const int ec = LastError(); | ||||
|         UNREACHABLE_MSG("Unhandled host socket error={}", ec); | ||||
|         return {AcceptResult{}, Errno::SUCCESS}; | ||||
|         return {AcceptResult{}, GetAndLogLastError()}; | ||||
|     } | ||||
|  | ||||
|     AcceptResult result; | ||||
| @@ -412,23 +452,14 @@ Errno Socket::Connect(SockAddrIn addr_in) { | ||||
|         return Errno::SUCCESS; | ||||
|     } | ||||
|  | ||||
|     switch (const int ec = LastError()) { | ||||
|     case WSAEWOULDBLOCK: | ||||
|         LOG_DEBUG(Service, "EAGAIN generated"); | ||||
|         return Errno::AGAIN; | ||||
|     default: | ||||
|         UNREACHABLE_MSG("Unhandled host socket error={}", ec); | ||||
|         return Errno::SUCCESS; | ||||
|     } | ||||
|     return GetAndLogLastError(); | ||||
| } | ||||
|  | ||||
| std::pair<SockAddrIn, Errno> Socket::GetPeerName() { | ||||
|     sockaddr addr; | ||||
|     socklen_t addrlen = sizeof(addr); | ||||
|     if (getpeername(fd, &addr, &addrlen) == SOCKET_ERROR) { | ||||
|         const int ec = LastError(); | ||||
|         UNREACHABLE_MSG("Unhandled host socket error={}", ec); | ||||
|         return {SockAddrIn{}, Errno::SUCCESS}; | ||||
|         return {SockAddrIn{}, GetAndLogLastError()}; | ||||
|     } | ||||
|  | ||||
|     ASSERT(addrlen == sizeof(sockaddr_in)); | ||||
| @@ -439,9 +470,7 @@ std::pair<SockAddrIn, Errno> Socket::GetSockName() { | ||||
|     sockaddr addr; | ||||
|     socklen_t addrlen = sizeof(addr); | ||||
|     if (getsockname(fd, &addr, &addrlen) == SOCKET_ERROR) { | ||||
|         const int ec = LastError(); | ||||
|         UNREACHABLE_MSG("Unhandled host socket error={}", ec); | ||||
|         return {SockAddrIn{}, Errno::SUCCESS}; | ||||
|         return {SockAddrIn{}, GetAndLogLastError()}; | ||||
|     } | ||||
|  | ||||
|     ASSERT(addrlen == sizeof(sockaddr_in)); | ||||
| @@ -454,9 +483,7 @@ Errno Socket::Bind(SockAddrIn addr) { | ||||
|         return Errno::SUCCESS; | ||||
|     } | ||||
|  | ||||
|     const int ec = LastError(); | ||||
|     UNREACHABLE_MSG("Unhandled host socket error={}", ec); | ||||
|     return Errno::SUCCESS; | ||||
|     return GetAndLogLastError(); | ||||
| } | ||||
|  | ||||
| Errno Socket::Listen(s32 backlog) { | ||||
| @@ -464,9 +491,7 @@ Errno Socket::Listen(s32 backlog) { | ||||
|         return Errno::SUCCESS; | ||||
|     } | ||||
|  | ||||
|     const int ec = LastError(); | ||||
|     UNREACHABLE_MSG("Unhandled host socket error={}", ec); | ||||
|     return Errno::SUCCESS; | ||||
|     return GetAndLogLastError(); | ||||
| } | ||||
|  | ||||
| Errno Socket::Shutdown(ShutdownHow how) { | ||||
| @@ -489,14 +514,7 @@ Errno Socket::Shutdown(ShutdownHow how) { | ||||
|         return Errno::SUCCESS; | ||||
|     } | ||||
|  | ||||
|     switch (const int ec = LastError()) { | ||||
|     case WSAENOTCONN: | ||||
|         LOG_ERROR(Service, "ENOTCONN generated"); | ||||
|         return Errno::NOTCONN; | ||||
|     default: | ||||
|         UNREACHABLE_MSG("Unhandled host socket error={}", ec); | ||||
|         return Errno::SUCCESS; | ||||
|     } | ||||
|     return GetAndLogLastError(); | ||||
| } | ||||
|  | ||||
| std::pair<s32, Errno> Socket::Recv(int flags, std::vector<u8>& message) { | ||||
| @@ -509,17 +527,7 @@ std::pair<s32, Errno> Socket::Recv(int flags, std::vector<u8>& message) { | ||||
|         return {static_cast<s32>(result), Errno::SUCCESS}; | ||||
|     } | ||||
|  | ||||
|     switch (const int ec = LastError()) { | ||||
|     case WSAEWOULDBLOCK: | ||||
|         LOG_DEBUG(Service, "EAGAIN generated"); | ||||
|         return {-1, Errno::AGAIN}; | ||||
|     case WSAENOTCONN: | ||||
|         LOG_ERROR(Service, "ENOTCONN generated"); | ||||
|         return {-1, Errno::NOTCONN}; | ||||
|     default: | ||||
|         UNREACHABLE_MSG("Unhandled host socket error={}", ec); | ||||
|         return {0, Errno::SUCCESS}; | ||||
|     } | ||||
|     return {-1, GetAndLogLastError()}; | ||||
| } | ||||
|  | ||||
| std::pair<s32, Errno> Socket::RecvFrom(int flags, std::vector<u8>& message, SockAddrIn* addr) { | ||||
| @@ -541,17 +549,7 @@ std::pair<s32, Errno> Socket::RecvFrom(int flags, std::vector<u8>& message, Sock | ||||
|         return {static_cast<s32>(result), Errno::SUCCESS}; | ||||
|     } | ||||
|  | ||||
|     switch (const int ec = LastError()) { | ||||
|     case WSAEWOULDBLOCK: | ||||
|         LOG_DEBUG(Service, "EAGAIN generated"); | ||||
|         return {-1, Errno::AGAIN}; | ||||
|     case WSAENOTCONN: | ||||
|         LOG_ERROR(Service, "ENOTCONN generated"); | ||||
|         return {-1, Errno::NOTCONN}; | ||||
|     default: | ||||
|         UNREACHABLE_MSG("Unhandled host socket error={}", ec); | ||||
|         return {-1, Errno::SUCCESS}; | ||||
|     } | ||||
|     return {-1, GetAndLogLastError()}; | ||||
| } | ||||
|  | ||||
| std::pair<s32, Errno> Socket::Send(const std::vector<u8>& message, int flags) { | ||||
| @@ -564,18 +562,7 @@ std::pair<s32, Errno> Socket::Send(const std::vector<u8>& message, int flags) { | ||||
|         return {static_cast<s32>(result), Errno::SUCCESS}; | ||||
|     } | ||||
|  | ||||
|     const int ec = LastError(); | ||||
|     switch (ec) { | ||||
|     case WSAEWOULDBLOCK: | ||||
|         LOG_DEBUG(Service, "EAGAIN generated"); | ||||
|         return {-1, Errno::AGAIN}; | ||||
|     case WSAENOTCONN: | ||||
|         LOG_ERROR(Service, "ENOTCONN generated"); | ||||
|         return {-1, Errno::NOTCONN}; | ||||
|     default: | ||||
|         UNREACHABLE_MSG("Unhandled host socket error={}", ec); | ||||
|         return {-1, Errno::SUCCESS}; | ||||
|     } | ||||
|     return {-1, GetAndLogLastError()}; | ||||
| } | ||||
|  | ||||
| std::pair<s32, Errno> Socket::SendTo(u32 flags, const std::vector<u8>& message, | ||||
| @@ -597,9 +584,7 @@ std::pair<s32, Errno> Socket::SendTo(u32 flags, const std::vector<u8>& message, | ||||
|         return {static_cast<s32>(result), Errno::SUCCESS}; | ||||
|     } | ||||
|  | ||||
|     const int ec = LastError(); | ||||
|     UNREACHABLE_MSG("Unhandled host socket error={}", ec); | ||||
|     return {-1, Errno::SUCCESS}; | ||||
|     return {-1, GetAndLogLastError()}; | ||||
| } | ||||
|  | ||||
| Errno Socket::Close() { | ||||
| @@ -642,9 +627,7 @@ Errno Socket::SetNonBlock(bool enable) { | ||||
|     if (EnableNonBlock(fd, enable)) { | ||||
|         return Errno::SUCCESS; | ||||
|     } | ||||
|     const int ec = LastError(); | ||||
|     UNREACHABLE_MSG("Unhandled host socket error={}", ec); | ||||
|     return Errno::SUCCESS; | ||||
|     return GetAndLogLastError(); | ||||
| } | ||||
|  | ||||
| bool Socket::IsOpened() const { | ||||
|   | ||||
| @@ -7,6 +7,7 @@ | ||||
| #include <array> | ||||
| #include <utility> | ||||
|  | ||||
| #include "common/common_funcs.h" | ||||
| #include "common/common_types.h" | ||||
|  | ||||
| namespace Network { | ||||
| @@ -21,6 +22,11 @@ enum class Errno { | ||||
|     MFILE, | ||||
|     NOTCONN, | ||||
|     AGAIN, | ||||
|     CONNREFUSED, | ||||
|     HOSTUNREACH, | ||||
|     NETDOWN, | ||||
|     NETUNREACH, | ||||
|     OTHER, | ||||
| }; | ||||
|  | ||||
| /// Address families | ||||
|   | ||||
| @@ -5,6 +5,7 @@ add_executable(tests | ||||
|     common/param_package.cpp | ||||
|     common/ring_buffer.cpp | ||||
|     core/core_timing.cpp | ||||
|     core/network/network.cpp | ||||
|     tests.cpp | ||||
|     video_core/buffer_base.cpp | ||||
| ) | ||||
|   | ||||
							
								
								
									
										28
									
								
								src/tests/core/network/network.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/tests/core/network/network.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| // Copyright 2021 yuzu Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #include <catch2/catch.hpp> | ||||
|  | ||||
| #include "core/network/network.h" | ||||
| #include "core/network/sockets.h" | ||||
|  | ||||
| TEST_CASE("Network::Errors", "[core]") { | ||||
|     Network::NetworkInstance network_instance; // initialize network | ||||
|  | ||||
|     Network::Socket socks[2]; | ||||
|     for (Network::Socket& sock : socks) { | ||||
|         REQUIRE(sock.Initialize(Network::Domain::INET, Network::Type::STREAM, | ||||
|                                 Network::Protocol::TCP) == Network::Errno::SUCCESS); | ||||
|     } | ||||
|  | ||||
|     Network::SockAddrIn addr{ | ||||
|         Network::Domain::INET, | ||||
|         {127, 0, 0, 1}, | ||||
|         1, // hopefully nobody running this test has something listening on port 1 | ||||
|     }; | ||||
|     REQUIRE(socks[0].Connect(addr) == Network::Errno::CONNREFUSED); | ||||
|  | ||||
|     std::vector<u8> message{1, 2, 3, 4}; | ||||
|     REQUIRE(socks[1].Recv(0, message).second == Network::Errno::NOTCONN); | ||||
| } | ||||
		Reference in New Issue
	
	Block a user