diff --git a/src/game.c b/src/game.c index 684bab3c03..af57ec2ba1 100644 --- a/src/game.c +++ b/src/game.c @@ -259,7 +259,7 @@ void game_update() numUpdates = clamp(1, numUpdates, 4); } - if (network_get_mode() == NETWORK_MODE_CLIENT) { + if (network_get_mode() == NETWORK_MODE_CLIENT && network_get_status() == NETWORK_STATUS_CONNECTED) { if (network_get_server_tick() - RCT2_GLOBAL(RCT2_ADDRESS_CURRENT_TICKS, uint32) >= 10) { // make sure client doesn't fall behind the server too much numUpdates += 10; @@ -335,7 +335,7 @@ void game_update() void game_logic_update() { network_update(); - if (network_get_mode() == NETWORK_MODE_CLIENT) { + if (network_get_mode() == NETWORK_MODE_CLIENT && network_get_status() == NETWORK_STATUS_CONNECTED) { if (RCT2_GLOBAL(RCT2_ADDRESS_CURRENT_TICKS, uint32) >= network_get_server_tick()) { // dont run past the server return; diff --git a/src/interface/chat.c b/src/interface/chat.c index 863327ddcc..43d66acc2d 100644 --- a/src/interface/chat.c +++ b/src/interface/chat.c @@ -59,6 +59,9 @@ void chat_update() void chat_draw() { + if (network_get_mode() == NETWORK_MODE_NONE) { + return; + } rct_drawpixelinfo *dpi = (rct_drawpixelinfo*)RCT2_ADDRESS_SCREEN_DPI; _chatLeft = 10; _chatTop = RCT2_GLOBAL(RCT2_ADDRESS_SCREEN_HEIGHT, uint16) - 40 - ((CHAT_HISTORY_SIZE + 1) * 10); @@ -108,7 +111,9 @@ void chat_input(int c) { switch (c) { case SDL_SCANCODE_RETURN: - network_send_chat(_chatCurrentLine); + if (strlen(_chatCurrentLine) > 0) { + network_send_chat(_chatCurrentLine); + } chat_clear_input(); chat_close(); break; diff --git a/src/network/network.cpp b/src/network/network.cpp index 8aeb41dc62..f8a438ba5f 100644 --- a/src/network/network.cpp +++ b/src/network/network.cpp @@ -33,6 +33,7 @@ extern "C" { #include #include #include +#include extern "C" { #include "../config.h" #include "../game.h" @@ -41,6 +42,7 @@ extern "C" { #include "../localisation/date.h" #include "../localisation/localisation.h" #include "../scenario.h" +#include "../windows/error.h" } #pragma comment(lib, "Ws2_32.lib") @@ -155,6 +157,14 @@ NetworkConnection::NetworkConnection() { authstatus = NETWORK_AUTH_NONE; player = 0; + socket = INVALID_SOCKET; +} + +NetworkConnection::~NetworkConnection() +{ + if (socket != INVALID_SOCKET) { + closesocket(socket); + } } int NetworkConnection::ReadPacket() @@ -253,10 +263,78 @@ bool NetworkConnection::SetNonBlocking(SOCKET socket, bool on) #endif } +NetworkAddress::NetworkAddress() +{ + ss = std::make_shared(); + ss_len = std::make_shared(); + status = std::make_shared(); + *status = RESOLVE_NONE; +} + +void NetworkAddress::Resolve(const char* host, unsigned short port, bool nonblocking) +{ + // A non-blocking hostname resolver + *status = RESOLVE_INPROGRESS; + mutex = SDL_CreateMutex(); + cond = SDL_CreateCond(); + NetworkAddress::host = host; + NetworkAddress::port = port; + SDL_LockMutex(mutex); + SDL_Thread* thread = SDL_CreateThread(ResolveFunc, 0, this); + // The mutex/cond is to make sure ResolveFunc doesn't ever get a dangling pointer + SDL_CondWait(cond, mutex); + SDL_UnlockMutex(mutex); + SDL_DestroyCond(cond); + SDL_DestroyMutex(mutex); + if (!nonblocking) { + int status; + SDL_WaitThread(thread, &status); + } +} + +int NetworkAddress::GetResolveStatus(void) +{ + return *status; +} + +int NetworkAddress::ResolveFunc(void* pointer) +{ + // Copy data for thread safety + NetworkAddress * networkaddress = (NetworkAddress*)pointer; + SDL_LockMutex(networkaddress->mutex); + std::string host; + if (networkaddress->host) host = networkaddress->host; + std::string port = std::to_string(networkaddress->port); + std::shared_ptr ss = networkaddress->ss; + std::shared_ptr ss_len = networkaddress->ss_len; + std::shared_ptr status = networkaddress->status; + SDL_CondSignal(networkaddress->cond); + SDL_UnlockMutex(networkaddress->mutex); + + // Perform the resolve + addrinfo hints; + addrinfo* res; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + if (host.length() == 0) { + hints.ai_flags = AI_PASSIVE; + } + getaddrinfo(host.length() == 0 ? NULL : host.c_str(), port.c_str(), &hints, &res); + if (res) { + memcpy(&(*ss), res->ai_addr, res->ai_addrlen); + *ss_len = res->ai_addrlen; + *status = RESOLVE_OK; + } else { + *status = RESOLVE_FAILED; + } + return 0; +} + Network::Network() { wsa_initialized = false; mode = NETWORK_MODE_NONE; + status = NETWORK_STATUS_NONE; last_tick_sent_time = 0; last_ping_sent_time = 0; strcpy(password, ""); @@ -300,16 +378,15 @@ bool Network::Init() void Network::Close() { if (mode == NETWORK_MODE_CLIENT) { - closesocket(server_socket); + closesocket(server_connection.socket); } else if (mode == NETWORK_MODE_SERVER) { closesocket(listening_socket); - for(auto it = client_connection_list.begin(); it != client_connection_list.end(); it++) { - closesocket((*it)->socket); - } } mode = NETWORK_MODE_NONE; + status = NETWORK_STATUS_NONE; + server_connection.authstatus = NETWORK_AUTH_NONE; client_connection_list.clear(); game_command_queue.clear(); @@ -331,69 +408,39 @@ bool Network::BeginClient(const char* host, unsigned short port) if (!Init()) return false; - server_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - if (server_socket == INVALID_SOCKET) { - log_error("Unable to create socket."); - return false; - } + server_address.Resolve(host, port); + status = NETWORK_STATUS_RESOLVING; - sockaddr_in server_address; -#ifdef USE_INET_PTON - char address[64]; - if (!network_get_address(address, sizeof(address), host)) { - log_error("Unable to resolve hostname."); - return false; - } - - if (inet_pton(AF_INET, address, &server_address.sin_addr) != 1) { - return false; - } -#else - server_address.sin_addr.S_un.S_addr = inet_addr(network_getAddress((char *)host)); -#endif // USE_INET_PTON - server_address.sin_family = AF_INET; - server_address.sin_port = htons(port); - - if (connect(server_socket, (sockaddr*)&server_address, sizeof(server_address)) != 0) { - log_error("Unable to connect to host."); - return false; - } else { - printf("Connected to server!\n"); - } - - server_connection.socket = server_socket; - server_connection.SetTCPNoDelay(true); - if (!server_connection.SetNonBlocking(true)) { - closesocket(server_socket); - log_error("Failed to set non-blocking mode."); - return false; - } + window_network_status_open("Resolving..."); mode = NETWORK_MODE_CLIENT; - Client_Send_AUTH(OPENRCT2_VERSION, gConfigNetwork.player_name, ""); return true; } -bool Network::BeginServer(unsigned short port) +bool Network::BeginServer(unsigned short port, const char* address) { Close(); if (!Init()) return false; + NetworkAddress networkaddress; + networkaddress.Resolve(address, port, false); + log_verbose("Begin listening for clients"); - listening_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + listening_socket = socket(networkaddress.ss->ss_family, SOCK_STREAM, IPPROTO_TCP); if (listening_socket == INVALID_SOCKET) { log_error("Unable to create socket."); return false; } - sockaddr_in local_address; - local_address.sin_family = AF_INET; - local_address.sin_addr.s_addr = INADDR_ANY; - local_address.sin_port = htons(port); + // Turn off IPV6_V6ONLY so we can accept both v4 and v6 connections + int value = 0; + if (setsockopt(listening_socket, IPPROTO_IPV6, IPV6_V6ONLY, (const char*)&value, sizeof(value)) != 0) { + log_error("IPV6_V6ONLY failed. %d", LAST_SOCKET_ERROR()); + } - if (bind(listening_socket, (sockaddr*)&local_address, sizeof(local_address)) != 0) { + if (bind(listening_socket, (sockaddr*)&(*networkaddress.ss), (*networkaddress.ss_len)) != 0) { closesocket(listening_socket); log_error("Unable to bind to socket."); return false; @@ -426,6 +473,11 @@ int Network::GetMode() return mode; } +int Network::GetStatus() +{ + return status; +} + int Network::GetAuthStatus() { if (GetMode() == NETWORK_MODE_CLIENT) { @@ -470,11 +522,11 @@ void Network::UpdateServer() it++; } } - if (SDL_GetTicks() - last_tick_sent_time >= 25) { + if (SDL_TICKS_PASSED(SDL_GetTicks(), last_tick_sent_time + 25)) { last_tick_sent_time = SDL_GetTicks(); Server_Send_TICK(); } - if (SDL_GetTicks() - last_ping_sent_time >= 3000) { + if (SDL_TICKS_PASSED(SDL_GetTicks(), last_ping_sent_time + 3000)) { last_ping_sent_time = SDL_GetTicks(); Server_Send_PING(); Server_Send_PINGLIST(); @@ -497,18 +549,91 @@ void Network::UpdateServer() void Network::UpdateClient() { - if (!ProcessConnection(server_connection)) { - Close(); - } - ProcessGameCommandQueue(); + bool connectfailed = false; + switch(status){ + case NETWORK_STATUS_RESOLVING:{ + if(server_address.GetResolveStatus() == NetworkAddress::RESOLVE_OK){ + server_connection.socket = socket(server_address.ss->ss_family, SOCK_STREAM, IPPROTO_TCP); + if (server_connection.socket == INVALID_SOCKET) { + log_error("Unable to create socket."); + connectfailed = true; + break; + } - // Check synchronisation - if (!_desynchronised && !CheckSRAND(RCT2_GLOBAL(RCT2_ADDRESS_CURRENT_TICKS, uint32), RCT2_GLOBAL(RCT2_ADDRESS_SCENARIO_SRAND_0, uint32))) { - _desynchronised = true; - window_network_status_open("Network desync detected"); - if (!gConfigNetwork.stay_connected) { + server_connection.SetTCPNoDelay(true); + if (!server_connection.SetNonBlocking(true)) { + log_error("Failed to set non-blocking mode."); + connectfailed = true; + break; + } + + if (connect(server_connection.socket, (sockaddr *)&(*server_address.ss), (*server_address.ss_len)) == SOCKET_ERROR && (LAST_SOCKET_ERROR() == EINPROGRESS || LAST_SOCKET_ERROR() == EWOULDBLOCK)){ + window_network_status_open("Connecting..."); + server_connect_time = SDL_GetTicks(); + status = NETWORK_STATUS_CONNECTING; + } else { + log_error("connect() failed %d", LAST_SOCKET_ERROR()); + connectfailed = true; + break; + } + } else { + log_error("Could not resolve address."); + connectfailed = true; + } + }break; + case NETWORK_STATUS_CONNECTING:{ + int error = 0; + socklen_t len = sizeof(error); + getsockopt(server_connection.socket, SOL_SOCKET, SO_ERROR, (char*)&error, &len); + if (error != 0) { + log_error("Connection failed %d", error); + connectfailed = true; + break; + } + if (SDL_TICKS_PASSED(SDL_GetTicks(), server_connect_time + 3000)) { + log_error("Connection timed out."); + connectfailed = true; + break; + } + fd_set writeFD; + FD_ZERO(&writeFD); + FD_SET(server_connection.socket, &writeFD); + timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec = 0; + if (select(server_connection.socket + 1, NULL, &writeFD, NULL, &timeout) > 0) { + int error = 0; + socklen_t len = sizeof(error); + getsockopt(server_connection.socket, SOL_SOCKET, SO_ERROR, (char*)&error, &len); + if (error == 0) { + status = NETWORK_STATUS_CONNECTED; + Client_Send_AUTH(OPENRCT2_VERSION, gConfigNetwork.player_name, ""); + window_network_status_open("Authenticating..."); + } + } + }break; + case NETWORK_STATUS_CONNECTED: + if (!ProcessConnection(server_connection)) { + window_network_status_open("Connection closed"); Close(); } + ProcessGameCommandQueue(); + + // Check synchronisation + if (!_desynchronised && !CheckSRAND(RCT2_GLOBAL(RCT2_ADDRESS_CURRENT_TICKS, uint32), RCT2_GLOBAL(RCT2_ADDRESS_SCENARIO_SRAND_0, uint32))) { + _desynchronised = true; + window_network_status_open("Network desync detected"); + if (!gConfigNetwork.stay_connected) { + Close(); + } + } + break; + } + + if (connectfailed) { + Close(); + window_network_status_close(); + window_error_open(STR_UNABLE_TO_CONNECT_TO_SERVER, STR_NONE); } } @@ -1032,6 +1157,11 @@ int network_get_mode() return gNetwork.GetMode(); } +int network_get_status() +{ + return gNetwork.GetStatus(); +} + int network_get_authstatus() { return gNetwork.GetAuthStatus(); @@ -1125,44 +1255,9 @@ void network_kick_player(int playerId) gNetwork.KickPlayer(playerId); } -#ifdef USE_INET_PTON -static bool network_get_address(char *dst, size_t dstLength, const char *host) -{ - struct addrinfo *remoteHost; - - if (getaddrinfo(host, NULL, NULL, &remoteHost) != 0) { - // Failed to resolve host name - return false; - } - - for (; remoteHost != NULL; remoteHost = remoteHost->ai_next) { - if (remoteHost->ai_family != AF_INET) continue; - - struct sockaddr_in *ipv4SockAddr = (struct sockaddr_in*)remoteHost->ai_addr; - return inet_ntop(AF_INET, (void*)&ipv4SockAddr->sin_addr, dst, dstLength) != NULL; - } - - // No IPv4 addresses found for host name - return false; -} -#else -static char *network_getAddress(char *host) -{ - struct hostent *remoteHost; - struct in_addr addr; - remoteHost = gethostbyname(host); - if (remoteHost != NULL && remoteHost->h_addrtype == AF_INET && remoteHost->h_addr_list[0] != 0) { - addr.s_addr = *(u_long *)remoteHost->h_addr_list[0]; - return inet_ntoa(addr); - } - - return host; -} -#endif // USE_INET_PTON - - #else int network_get_mode() { return NETWORK_MODE_NONE; } +int network_get_status() { return NETWORK_STATUS_NONE; } uint32 network_get_server_tick() { return RCT2_GLOBAL(RCT2_ADDRESS_CURRENT_TICKS, uint32); } void network_send_gamecmd(uint32 eax, uint32 ebx, uint32 ecx, uint32 edx, uint32 esi, uint32 edi, uint32 ebp, uint8 callback) {} void network_send_map() {} diff --git a/src/network/network.h b/src/network/network.h index 12f1f84e47..a6c208fedc 100644 --- a/src/network/network.h +++ b/src/network/network.h @@ -31,6 +31,22 @@ enum { NETWORK_PLAYER_FLAG_ISSERVER = 1 << 0, }; +enum { + NETWORK_AUTH_NONE, + NETWORK_AUTH_REQUESTED, + NETWORK_AUTH_OK, + NETWORK_AUTH_BADVERSION, + NETWORK_AUTH_BADNAME, + NETWORK_AUTH_BADPASSWORD +}; + +enum { + NETWORK_STATUS_NONE, + NETWORK_STATUS_RESOLVING, + NETWORK_STATUS_CONNECTING, + NETWORK_STATUS_CONNECTED +}; + #define NETWORK_DEFAULT_PORT 11753 #ifdef __cplusplus @@ -44,17 +60,9 @@ extern "C" { #ifndef DISABLE_NETWORK -#ifndef __MINGW32__ -#define USE_INET_PTON -#else -#warning using deprecated network functions in lieu of inet_pton, inet_ntop -#endif // __MINGW32__ - #ifdef _WIN32 #include - #ifdef USE_INET_PTON - #include - #endif + #include #define LAST_SOCKET_ERROR() WSAGetLastError() #undef EWOULDBLOCK #define EWOULDBLOCK WSAEWOULDBLOCK @@ -72,15 +80,6 @@ extern "C" { #define ioctlsocket ioctl #endif // _WIN32 -enum { - NETWORK_AUTH_NONE, - NETWORK_AUTH_REQUESTED, - NETWORK_AUTH_OK, - NETWORK_AUTH_BADVERSION, - NETWORK_AUTH_BADNAME, - NETWORK_AUTH_BADPASSWORD -}; - #ifdef __cplusplus #include @@ -137,6 +136,7 @@ class NetworkConnection { public: NetworkConnection(); + ~NetworkConnection(); int ReadPacket(); void QueuePacket(std::unique_ptr packet); void SendQueuedPackets(); @@ -155,6 +155,33 @@ private: std::list> outboundpackets; }; +class NetworkAddress +{ +public: + NetworkAddress(); + void Resolve(const char* host, unsigned short port, bool nonblocking = true); + int GetResolveStatus(void); + + std::shared_ptr ss; + std::shared_ptr ss_len; + + enum { + RESOLVE_NONE, + RESOLVE_INPROGRESS, + RESOLVE_OK, + RESOLVE_FAILED + }; + +private: + static int ResolveFunc(void* pointer); + + const char* host; + unsigned short port; + SDL_mutex* mutex; + SDL_cond* cond; + std::shared_ptr status; +}; + class Network { public: @@ -163,8 +190,9 @@ public: bool Init(); void Close(); bool BeginClient(const char* host, unsigned short port); - bool BeginServer(unsigned short port); + bool BeginServer(unsigned short port, const char* address = NULL); int GetMode(); + int GetStatus(); int GetAuthStatus(); uint32 GetServerTick(); uint8 GetPlayerID(); @@ -211,8 +239,9 @@ private: }; int mode; + int status; + NetworkAddress server_address; bool wsa_initialized; - SOCKET server_socket; SOCKET listening_socket; NetworkConnection server_connection; uint32 last_tick_sent_time; @@ -226,6 +255,8 @@ private: std::vector chunk_buffer; char password[33]; bool _desynchronised; + uint32 server_connect_time; + void UpdateServer(); void UpdateClient(); @@ -259,6 +290,7 @@ int network_begin_client(const char *host, int port); int network_begin_server(int port); int network_get_mode(); +int network_get_status(); void network_update(); int network_get_authstatus(); uint32 network_get_server_tick(); @@ -276,11 +308,6 @@ void network_send_gamecmd(uint32 eax, uint32 ebx, uint32 ecx, uint32 edx, uint32 void network_kick_player(int playerId); void network_print_error(); -#ifdef USE_INET_PTON -static bool network_get_address(char *dst, size_t dstLength, const char *host); -#else -static char *network_getAddress(char *host); -#endif // USE_INET_PTON #ifdef __cplusplus } diff --git a/src/windows/server_list.c b/src/windows/server_list.c index 8cd531f841..2be9576779 100644 --- a/src/windows/server_list.c +++ b/src/windows/server_list.c @@ -524,17 +524,29 @@ static void join_server(char *address, bool spectate) { int port = gConfigNetwork.default_port; - char *colon = strchr(address, ':'); - if (colon != NULL) { + bool addresscopied = false; + + char *endbracket = strrchr(address, ']'); + char *startbracket = strrchr(address, '['); + char *dot = strchr(address, '.'); + + char *colon = strrchr(address, ':'); + if (colon != NULL && (endbracket != NULL || dot != NULL)) { address = substr(address, colon - address); sscanf(colon + 1, "%d", &port); + addresscopied = true; + } + + if (startbracket && endbracket) { + address = substr(startbracket + 1, endbracket - startbracket - 1); + addresscopied = true; } if (!network_begin_client(address, port)) { window_error_open(STR_UNABLE_TO_CONNECT_TO_SERVER, STR_NONE); } - if (colon != NULL) { + if (addresscopied) { free(address); } }