1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2026-01-17 03:53:07 +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

@@ -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() {}