diff --git a/src/crashlog.cpp b/src/crashlog.cpp index 9582551884..baef530cab 100644 --- a/src/crashlog.cpp +++ b/src/crashlog.cpp @@ -610,7 +610,7 @@ bool CrashLog::MakeCrashLog() const * information like paths to the console. * @return true when everything is made successfully. */ -bool CrashLog::MakeDesyncCrashLog() const +bool CrashLog::MakeDesyncCrashLog(const std::string *log_in, std::string *log_out) const { char filename[MAX_PATH]; char buffer[65536 * 2]; @@ -624,7 +624,14 @@ bool CrashLog::MakeDesyncCrashLog() const strftime(name_buffer_date, lastof(name_buffer) - name_buffer_date, "%Y%m%dT%H%M%SZ", gmtime(&cur_time)); printf("Desync encountered (%s), generating desync log...\n", mode); - this->FillDesyncCrashLog(buffer, lastof(buffer)); + char *b = this->FillDesyncCrashLog(buffer, lastof(buffer)); + + if (log_in && !log_in->empty()) { + b = strecpy(b, "\n", lastof(buffer), true); + b = strecpy(b, log_in->c_str(), lastof(buffer), true); + } + + if (log_out) log_out->assign(buffer); bool bret = this->WriteCrashLog(buffer, filename, lastof(filename), name_buffer); if (bret) { diff --git a/src/crashlog.h b/src/crashlog.h index 24ee2d5f67..a7d06c69b2 100644 --- a/src/crashlog.h +++ b/src/crashlog.h @@ -128,7 +128,7 @@ public: bool WriteScreenshot(char *filename, const char *filename_last, const char *name = "crash") const; bool MakeCrashLog() const; - bool MakeDesyncCrashLog() const; + bool MakeDesyncCrashLog(const std::string *log_in, std::string *log_out) const; bool MakeCrashSavegameAndScreenshot() const; /** @@ -138,7 +138,7 @@ public: */ static void InitialiseCrashLog(); - static void DesyncCrashLog(); + static void DesyncCrashLog(const std::string *log_in, std::string *log_out); static void SetErrorMessage(const char *message); static void AfterCrashLogCleanup(); diff --git a/src/network/core/os_abstraction.h b/src/network/core/os_abstraction.h index a8df976d51..5c0a5f2e6b 100644 --- a/src/network/core/os_abstraction.h +++ b/src/network/core/os_abstraction.h @@ -62,6 +62,9 @@ typedef unsigned long in_addr_t; # define ioctlsocket ioctl # define closesocket close # define GET_LAST_ERROR() (errno) +# define SD_RECEIVE SHUT_RD +# define SD_SEND SHUT_WR +# define SD_BOTH SHUT_RDWR /* Need this for FIONREAD on solaris */ # define BSD_COMP @@ -102,6 +105,9 @@ typedef unsigned long in_addr_t; # define ioctlsocket ioctl # define closesocket close # define GET_LAST_ERROR() (sock_errno()) +# define SD_RECEIVE SHUT_RD +# define SD_SEND SHUT_WR +# define SD_BOTH SHUT_RDWR /* Includes needed for OS/2 systems */ # include @@ -166,6 +172,21 @@ static inline bool SetNonBlocking(SOCKET d) return ioctlsocket(d, FIONBIO, &nonblocking) == 0; } +/** + * Try to set the socket into blocking mode. + * @param d The socket to set the blocking more for. + * @return True if setting the blocking mode succeeded, otherwise false. + */ +static inline bool SetBlocking(SOCKET d) +{ +#ifdef _WIN32 + u_long nonblocking = 0; +#else + int nonblocking = 0; +#endif + return ioctlsocket(d, FIONBIO, &nonblocking) == 0; +} + /** * Try to set the socket to not delay sending. * @param d The socket to disable the delaying for. @@ -179,6 +200,32 @@ static inline bool SetNoDelay(SOCKET d) return setsockopt(d, IPPROTO_TCP, TCP_NODELAY, (const char*)&b, sizeof(b)) == 0; } + +/** + * Try to shutdown the socket in one or both directions. + * @param d The socket to disable the delaying for. + * @param read Whether to shutdown the read direction. + * @param write Whether to shutdown the write direction. + * @param linger_timeout The socket linger timeout. + * @return True if successful + */ +static inline bool ShutdownSocket(SOCKET d, bool read, bool write, uint linger_timeout) +{ + if (!read && !write) return true; +#ifdef _WIN32 + LINGER ln = { 1U, (uint16) linger_timeout }; +#else + struct linger ln = { 1, (int) linger_timeout }; +#endif + + setsockopt(d, SOL_SOCKET, SO_LINGER, (const char*)&ln, sizeof(ln)); + + int how = SD_BOTH; + if (!read) how = SD_SEND; + if (!write) how = SD_RECEIVE; + return shutdown(d, how) == 0; +} + /* Make sure these structures have the size we expect them to be */ assert_compile(sizeof(in_addr) == 4); ///< IPv4 addresses should be 4 bytes. assert_compile(sizeof(in6_addr) == 16); ///< IPv6 addresses should be 16 bytes. diff --git a/src/network/core/tcp_game.cpp b/src/network/core/tcp_game.cpp index 6d69d10d25..ef3a382219 100644 --- a/src/network/core/tcp_game.cpp +++ b/src/network/core/tcp_game.cpp @@ -99,6 +99,7 @@ NetworkRecvStatus NetworkGameSocketHandler::HandlePacket(Packet *p) case PACKET_CLIENT_SET_NAME: return this->Receive_CLIENT_SET_NAME(p); case PACKET_CLIENT_QUIT: return this->Receive_CLIENT_QUIT(p); case PACKET_CLIENT_ERROR: return this->Receive_CLIENT_ERROR(p); + case PACKET_CLIENT_DESYNC_LOG: return this->Receive_CLIENT_DESYNC_LOG(p); case PACKET_SERVER_QUIT: return this->Receive_SERVER_QUIT(p); case PACKET_SERVER_ERROR_QUIT: return this->Receive_SERVER_ERROR_QUIT(p); case PACKET_SERVER_SHUTDOWN: return this->Receive_SERVER_SHUTDOWN(p); @@ -185,6 +186,7 @@ NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_SET_PASSWORD(Packet * NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_SET_NAME(Packet *p) { return this->ReceiveInvalidPacket(PACKET_CLIENT_SET_NAME); } NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_QUIT(Packet *p) { return this->ReceiveInvalidPacket(PACKET_CLIENT_QUIT); } NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_ERROR(Packet *p) { return this->ReceiveInvalidPacket(PACKET_CLIENT_ERROR); } +NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_DESYNC_LOG(Packet *p) { return this->ReceiveInvalidPacket(PACKET_CLIENT_DESYNC_LOG); } NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_QUIT(Packet *p) { return this->ReceiveInvalidPacket(PACKET_SERVER_QUIT); } NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_ERROR_QUIT(Packet *p) { return this->ReceiveInvalidPacket(PACKET_SERVER_ERROR_QUIT); } NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_SHUTDOWN(Packet *p) { return this->ReceiveInvalidPacket(PACKET_SERVER_SHUTDOWN); } diff --git a/src/network/core/tcp_game.h b/src/network/core/tcp_game.h index df0e925f18..03eb5ded44 100644 --- a/src/network/core/tcp_game.h +++ b/src/network/core/tcp_game.h @@ -121,6 +121,7 @@ enum PacketGameType { PACKET_SERVER_QUIT, ///< A server tells that a client has quit. PACKET_CLIENT_ERROR, ///< A client reports an error to the server. PACKET_SERVER_ERROR_QUIT, ///< A server tells that a client has hit an error and did quit. + PACKET_CLIENT_DESYNC_LOG, ///< A client reports a desync log PACKET_END, ///< Must ALWAYS be on the end of this list!! (period) }; @@ -426,6 +427,7 @@ protected: * @param p The packet that was just received. */ virtual NetworkRecvStatus Receive_CLIENT_ERROR(Packet *p); + virtual NetworkRecvStatus Receive_CLIENT_DESYNC_LOG(Packet *p); /** * Notification that a client left the game: diff --git a/src/network/network_client.cpp b/src/network/network_client.cpp index a91a5f90e2..f22e4c3fff 100644 --- a/src/network/network_client.cpp +++ b/src/network/network_client.cpp @@ -174,16 +174,22 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::CloseConnection(NetworkRecvSta */ if (this->sock == INVALID_SOCKET) return status; - DEBUG(net, 1, "Closed client connection %d", this->client_id); + DEBUG(net, 1, "Shutting down client connection %d", this->client_id); + + SetBlocking(this->sock); this->SendPackets(true); + ShutdownSocket(this->sock, false, true, 2); + /* Wait a number of ticks so our leave message can reach the server. * This is especially needed for Windows servers as they seem to get * the "socket is closed" message before receiving our leave message, * which would trigger the server to close the connection as well. */ CSleep(3 * MILLISECONDS_PER_TICK); + DEBUG(net, 1, "Shutdown client connection %d", this->client_id); + delete this->GetInfo(); delete this; @@ -223,6 +229,8 @@ void ClientNetworkGameSocketHandler::ClientError(NetworkRecvStatus res) SendError(errorno); } + this->SendPackets(); + ClientNetworkEmergencySave(); _switch_mode = SM_MENU; @@ -281,9 +289,11 @@ void ClientNetworkGameSocketHandler::ClientError(NetworkRecvStatus res) NetworkError(STR_NETWORK_ERROR_DESYNC); DEBUG(desync, 1, "sync_err: date{%08x; %02x; %02x}", _date, _date_fract, _tick_skip_counter); DEBUG(net, 0, "Sync error detected!"); - my_client->ClientError(NETWORK_RECV_STATUS_DESYNC); - CrashLog::DesyncCrashLog(); + std::string desync_log; + CrashLog::DesyncCrashLog(nullptr, &desync_log); + my_client->SendDesyncLog(desync_log); + my_client->ClientError(NETWORK_RECV_STATUS_DESYNC); return false; } @@ -469,6 +479,22 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendError(NetworkErrorCode err return NETWORK_RECV_STATUS_OKAY; } +/** Send an error-packet over the network */ +NetworkRecvStatus ClientNetworkGameSocketHandler::SendDesyncLog(const std::string &log) +{ + for (size_t offset = 0; offset < log.size();) { + size_t size = min(log.size() - offset, SHRT_MAX - 2); + + Packet *p = new Packet(PACKET_CLIENT_DESYNC_LOG); + p->Send_uint16(size); + p->Send_binary(log.data() + offset, size); + my_client->SendPacket(p); + + offset += size; + } + return NETWORK_RECV_STATUS_OKAY; +} + /** * Tell the server that we like to change the password of the company. * @param password The new password. diff --git a/src/network/network_client.h b/src/network/network_client.h index 4fc27571b9..25ee42d426 100644 --- a/src/network/network_client.h +++ b/src/network/network_client.h @@ -86,6 +86,7 @@ public: static NetworkRecvStatus SendJoin(); static NetworkRecvStatus SendCommand(const CommandPacket *cp); static NetworkRecvStatus SendError(NetworkErrorCode errorno); + static NetworkRecvStatus SendDesyncLog(const std::string &log); static NetworkRecvStatus SendQuit(); static NetworkRecvStatus SendAck(); diff --git a/src/network/network_server.cpp b/src/network/network_server.cpp index 5337eb95f2..0613d47e8a 100644 --- a/src/network/network_server.cpp +++ b/src/network/network_server.cpp @@ -1171,7 +1171,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_ERROR(Packet *p NetworkAdminClientError(this->client_id, errorno); if (errorno == NETWORK_ERROR_DESYNC) { - CrashLog::DesyncCrashLog(); + CrashLog::DesyncCrashLog(&(this->desync_log), nullptr); // have the server and all clients run some sanity checks NetworkSendCommand(0, 0, 0, CMD_DESYNC_CHECK, nullptr, nullptr, _local_company, 0); @@ -1180,6 +1180,16 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_ERROR(Packet *p return this->CloseConnection(NETWORK_RECV_STATUS_CONN_LOST); } +NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_DESYNC_LOG(Packet *p) +{ + uint size = p->Recv_uint16(); + this->desync_log.resize(this->desync_log.size() + size); + p->Recv_binary(const_cast(this->desync_log.data() + this->desync_log.size() - size), size); + DEBUG(net, 2, "Received %u bytes of client desync log", size); + this->receive_limit += p->size; + return NETWORK_RECV_STATUS_OKAY; +} + NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_QUIT(Packet *p) { /* The client wants to leave. Display this and report it to the other diff --git a/src/network/network_server.h b/src/network/network_server.h index e985bd535c..89de84d06b 100644 --- a/src/network/network_server.h +++ b/src/network/network_server.h @@ -38,6 +38,7 @@ protected: NetworkRecvStatus Receive_CLIENT_SET_NAME(Packet *p) override; NetworkRecvStatus Receive_CLIENT_QUIT(Packet *p) override; NetworkRecvStatus Receive_CLIENT_ERROR(Packet *p) override; + NetworkRecvStatus Receive_CLIENT_DESYNC_LOG(Packet *p) override; NetworkRecvStatus Receive_CLIENT_RCON(Packet *p) override; NetworkRecvStatus Receive_CLIENT_NEWGRFS_CHECKED(Packet *p) override; NetworkRecvStatus Receive_CLIENT_MOVE(Packet *p) override; @@ -75,6 +76,8 @@ public: struct PacketWriter *savegame; ///< Writer used to write the savegame. NetworkAddress client_address; ///< IP-address of the client (so he can be banned) + std::string desync_log; + ServerNetworkGameSocketHandler(SOCKET s); ~ServerNetworkGameSocketHandler(); diff --git a/src/os/macosx/crashlog_osx.cpp b/src/os/macosx/crashlog_osx.cpp index a70d8acf5b..329f7c50a1 100644 --- a/src/os/macosx/crashlog_osx.cpp +++ b/src/os/macosx/crashlog_osx.cpp @@ -516,8 +516,8 @@ void CDECL HandleCrash(int signum, siginfo_t *si, void *context) } } -/* static */ void CrashLog::DesyncCrashLog() +/* static */ void CrashLog::DesyncCrashLog(const std::string *log_in, std::string *log_out) { CrashLogOSX log(CrashLogOSX::DesyncTag{}); - log.MakeDesyncCrashLog(); + log.MakeDesyncCrashLog(log_in, log_out); } diff --git a/src/os/unix/crashlog_unix.cpp b/src/os/unix/crashlog_unix.cpp index 9d05a8a087..7883f74b29 100644 --- a/src/os/unix/crashlog_unix.cpp +++ b/src/os/unix/crashlog_unix.cpp @@ -620,8 +620,8 @@ static void CDECL HandleCrash(int signum) } } -/* static */ void CrashLog::DesyncCrashLog() +/* static */ void CrashLog::DesyncCrashLog(const std::string *log_in, std::string *log_out) { CrashLogUnix log(CrashLogUnix::DesyncTag{}); - log.MakeDesyncCrashLog(); + log.MakeDesyncCrashLog(log_in, log_out); } diff --git a/src/os/windows/crashlog_win.cpp b/src/os/windows/crashlog_win.cpp index 1b4db6d287..8f82cb46d0 100644 --- a/src/os/windows/crashlog_win.cpp +++ b/src/os/windows/crashlog_win.cpp @@ -612,10 +612,10 @@ static void CDECL CustomAbort(int signal) SetUnhandledExceptionFilter(ExceptionHandler); } -/* static */ void CrashLog::DesyncCrashLog() +/* static */ void CrashLog::DesyncCrashLog(const std::string *log_in, std::string *log_out) { CrashLogWindows log(nullptr); - log.MakeDesyncCrashLog(); + log.MakeDesyncCrashLog(log_in, log_out); } /* The crash log GUI */