1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2026-01-16 19:43:06 +01:00

ipv6 support, non-blocking address resolve and connect

This commit is contained in:
zsilencer
2015-10-26 21:43:33 -06:00
parent 680bb0c52b
commit 00e2ca43a1
5 changed files with 265 additions and 126 deletions

View File

@@ -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;

View File

@@ -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;

View File

@@ -33,6 +33,7 @@ extern "C" {
#include <math.h>
#include <algorithm>
#include <set>
#include <string>
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<sockaddr_storage>();
ss_len = std::make_shared<int>();
status = std::make_shared<int>();
*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<sockaddr_storage> ss = networkaddress->ss;
std::shared_ptr<int> ss_len = networkaddress->ss_len;
std::shared_ptr<int> 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() {}

View File

@@ -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 <winsock2.h>
#ifdef USE_INET_PTON
#include <ws2tcpip.h>
#endif
#include <ws2tcpip.h>
#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 <list>
@@ -137,6 +136,7 @@ class NetworkConnection
{
public:
NetworkConnection();
~NetworkConnection();
int ReadPacket();
void QueuePacket(std::unique_ptr<NetworkPacket> packet);
void SendQueuedPackets();
@@ -155,6 +155,33 @@ private:
std::list<std::unique_ptr<NetworkPacket>> outboundpackets;
};
class NetworkAddress
{
public:
NetworkAddress();
void Resolve(const char* host, unsigned short port, bool nonblocking = true);
int GetResolveStatus(void);
std::shared_ptr<sockaddr_storage> ss;
std::shared_ptr<int> 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<int> 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<uint8> 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
}

View File

@@ -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);
}
}