Merge branch 'master' into jgrpp-beta

# Conflicts:
#	.github/workflows/ci-build.yml
#	src/lang/german.txt
#	src/lang/romanian.txt
#	src/lang/slovak.txt
#	src/lang/turkish.txt
#	src/network/core/address.cpp
#	src/network/core/tcp.h
#	src/network/core/udp.cpp
#	src/network/network.cpp
#	src/network/network_client.cpp
#	src/network/network_server.cpp
#	src/network/network_server.h
#	src/network/network_udp.cpp
#	src/openttd.cpp
#	src/saveload/newgrf_sl.cpp
#	src/tree_cmd.cpp
#	src/video/video_driver.hpp
#	src/window.cpp
#	src/window_gui.h
This commit is contained in:
Jonathan G Rennison
2021-09-22 21:49:32 +01:00
76 changed files with 1226 additions and 617 deletions

View File

@@ -14,8 +14,6 @@
#include "../../safeguards.h"
static const int DEFAULT_CONNECT_TIMEOUT_SECONDS = 3; ///< Allow connect() three seconds to connect.
/**
* Get the hostname; in case it wasn't given the
* IPv4 dotted representation is given.
@@ -316,82 +314,6 @@ SOCKET NetworkAddress::Resolve(int family, int socktype, int flags, SocketList *
return sock;
}
/**
* Helper function to resolve a connected socket.
* @param runp information about the socket to try not
* @return the opened socket or INVALID_SOCKET
*/
static SOCKET ConnectLoopProc(addrinfo *runp)
{
const char *type = NetworkAddress::SocketTypeAsString(runp->ai_socktype);
const char *family = NetworkAddress::AddressFamilyAsString(runp->ai_family);
std::string address = NetworkAddress(runp->ai_addr, (int)runp->ai_addrlen).GetAddressAsString();
SOCKET sock = socket(runp->ai_family, runp->ai_socktype, runp->ai_protocol);
if (sock == INVALID_SOCKET) {
DEBUG(net, 1, "[%s] could not create %s socket: %s", type, family, NetworkError::GetLast().AsString());
return INVALID_SOCKET;
}
if (!SetNoDelay(sock)) DEBUG(net, 1, "[%s] setting TCP_NODELAY failed", type);
if (!SetNonBlocking(sock)) DEBUG(net, 0, "[%s] setting non-blocking mode failed", type);
int err = connect(sock, runp->ai_addr, (int)runp->ai_addrlen);
if (err != 0 && !NetworkError::GetLast().IsConnectInProgress()) {
DEBUG(net, 1, "[%s] could not connect to %s over %s: %s", type, address.c_str(), family, NetworkError::GetLast().AsString());
closesocket(sock);
return INVALID_SOCKET;
}
fd_set write_fd;
struct timeval tv;
FD_ZERO(&write_fd);
FD_SET(sock, &write_fd);
/* Wait for connect() to either connect, timeout or fail. */
tv.tv_usec = 0;
tv.tv_sec = DEFAULT_CONNECT_TIMEOUT_SECONDS;
int n = select(FD_SETSIZE, NULL, &write_fd, NULL, &tv);
if (n < 0) {
DEBUG(net, 1, "[%s] could not connect to %s: %s", type, address.c_str(), NetworkError::GetLast().AsString());
closesocket(sock);
return INVALID_SOCKET;
}
/* If no fd is selected, the timeout has been reached. */
if (n == 0) {
DEBUG(net, 1, "[%s] timed out while connecting to %s", type, address.c_str());
closesocket(sock);
return INVALID_SOCKET;
}
/* Retrieve last error, if any, on the socket. */
NetworkError socket_error = GetSocketError(sock);
if (socket_error.HasError()) {
DEBUG(net, 1, "[%s] could not connect to %s: %s", type, address.c_str(), socket_error.AsString());
closesocket(sock);
return INVALID_SOCKET;
}
/* Connection succeeded. */
DEBUG(net, 1, "[%s] connected to %s", type, address.c_str());
return sock;
}
/**
* Connect to the given address.
* @return the connected socket or INVALID_SOCKET.
*/
SOCKET NetworkAddress::Connect()
{
DEBUG(net, 1, "Connecting to %s", NetworkAddressDumper().GetAddressAsString(this));
return this->Resolve(AF_UNSPEC, SOCK_STREAM, AI_ADDRCONFIG, nullptr, ConnectLoopProc);
}
/**
* Helper function to resolve a listening.
* @param runp information about the socket to try not
@@ -399,49 +321,52 @@ SOCKET NetworkAddress::Connect()
*/
static SOCKET ListenLoopProc(addrinfo *runp)
{
const char *type = NetworkAddress::SocketTypeAsString(runp->ai_socktype);
const char *family = NetworkAddress::AddressFamilyAsString(runp->ai_family);
std::string address = NetworkAddress(runp->ai_addr, (int)runp->ai_addrlen).GetAddressAsString();
SOCKET sock = socket(runp->ai_family, runp->ai_socktype, runp->ai_protocol);
if (sock == INVALID_SOCKET) {
DEBUG(net, 0, "[%s] could not create %s socket on port %s: %s", type, family, address.c_str(), NetworkError::GetLast().AsString());
const char *type = NetworkAddress::SocketTypeAsString(runp->ai_socktype);
const char *family = NetworkAddress::AddressFamilyAsString(runp->ai_family);
DEBUG(net, 0, "Could not create %s %s socket: %s", type, family, NetworkError::GetLast().AsString());
return INVALID_SOCKET;
}
if (runp->ai_socktype == SOCK_STREAM && !SetNoDelay(sock)) {
DEBUG(net, 3, "[%s] setting TCP_NODELAY failed for port %s", type, address.c_str());
DEBUG(net, 1, "Setting no-delay mode failed: %s", NetworkError::GetLast().AsString());
}
int on = 1;
/* The (const char*) cast is needed for windows!! */
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, sizeof(on)) == -1) {
DEBUG(net, 3, "[%s] could not set reusable %s sockets for port %s: %s", type, family, address.c_str(), NetworkError::GetLast().AsString());
DEBUG(net, 0, "Setting reuse-address mode failed: %s", NetworkError::GetLast().AsString());
}
#ifndef __OS2__
if (runp->ai_family == AF_INET6 &&
setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (const char*)&on, sizeof(on)) == -1) {
DEBUG(net, 3, "[%s] could not disable IPv4 over IPv6 on port %s: %s", type, address.c_str(), NetworkError::GetLast().AsString());
DEBUG(net, 3, "Could not disable IPv4 over IPv6: %s", NetworkError::GetLast().AsString());
}
#endif
if (bind(sock, runp->ai_addr, (int)runp->ai_addrlen) != 0) {
DEBUG(net, 1, "[%s] could not bind on %s port %s: %s", type, family, address.c_str(), NetworkError::GetLast().AsString());
DEBUG(net, 0, "Could not bind socket on %s: %s", address.c_str(), NetworkError::GetLast().AsString());
closesocket(sock);
return INVALID_SOCKET;
}
if (runp->ai_socktype != SOCK_DGRAM && listen(sock, 1) != 0) {
DEBUG(net, 1, "[%s] could not listen at %s port %s: %s", type, family, address.c_str(), NetworkError::GetLast().AsString());
DEBUG(net, 0, "Could not listen on socket: %s", NetworkError::GetLast().AsString());
closesocket(sock);
return INVALID_SOCKET;
}
/* Connection succeeded */
if (!SetNonBlocking(sock)) DEBUG(net, 0, "[%s] setting non-blocking mode failed for %s port %s", type, family, address.c_str());
DEBUG(net, 1, "[%s] listening on %s port %s", type, family, address.c_str());
if (!SetNonBlocking(sock)) {
DEBUG(net, 0, "Setting non-blocking mode failed: %s", NetworkError::GetLast().AsString());
}
DEBUG(net, 3, "Listening on %s", address.c_str());
return sock;
}
@@ -496,3 +421,16 @@ void NetworkAddress::Listen(int socktype, SocketList *sockets)
default: return "unsupported";
}
}
/**
* Get the peer name of a socket in string format.
* @param sock The socket to get the peer name of.
* @return The string representation of the peer name.
*/
/* static */ const std::string NetworkAddress::GetPeerName(SOCKET sock)
{
sockaddr_storage addr;
socklen_t addr_len = sizeof(addr);
getpeername(sock, (sockaddr *)&addr, &addr_len);
return NetworkAddress(addr, addr_len).GetAddressAsString();
}

View File

@@ -171,11 +171,11 @@ public:
return this->CompareTo(address) < 0;
}
SOCKET Connect();
void Listen(int socktype, SocketList *sockets);
static const char *SocketTypeAsString(int socktype);
static const char *AddressFamilyAsString(int family);
static const std::string GetPeerName(SOCKET sock);
};
/**

View File

@@ -27,9 +27,9 @@ bool NetworkCoreInitialize()
#ifdef _WIN32
{
WSADATA wsa;
DEBUG(net, 3, "[core] loading windows socket library");
DEBUG(net, 5, "Loading windows socket library");
if (WSAStartup(MAKEWORD(2, 0), &wsa) != 0) {
DEBUG(net, 0, "[core] WSAStartup failed, network unavailable");
DEBUG(net, 0, "WSAStartup failed, network unavailable");
return false;
}
}

View File

@@ -20,16 +20,17 @@ void NetworkCoreShutdown();
/** Status of a network client; reasons why a client has quit */
enum NetworkRecvStatus {
NETWORK_RECV_STATUS_OKAY, ///< Everything is okay
NETWORK_RECV_STATUS_DESYNC, ///< A desync did occur
NETWORK_RECV_STATUS_NEWGRF_MISMATCH, ///< We did not have the required NewGRFs
NETWORK_RECV_STATUS_SAVEGAME, ///< Something went wrong (down)loading the savegame
NETWORK_RECV_STATUS_CONN_LOST, ///< The connection is 'just' lost
NETWORK_RECV_STATUS_MALFORMED_PACKET, ///< We apparently send a malformed packet
NETWORK_RECV_STATUS_SERVER_ERROR, ///< The server told us we made an error
NETWORK_RECV_STATUS_SERVER_FULL, ///< The server is full
NETWORK_RECV_STATUS_SERVER_BANNED, ///< The server has banned us
NETWORK_RECV_STATUS_CLOSE_QUERY, ///< Done querying the server
NETWORK_RECV_STATUS_OKAY, ///< Everything is okay.
NETWORK_RECV_STATUS_DESYNC, ///< A desync did occur.
NETWORK_RECV_STATUS_NEWGRF_MISMATCH, ///< We did not have the required NewGRFs.
NETWORK_RECV_STATUS_SAVEGAME, ///< Something went wrong (down)loading the savegame.
NETWORK_RECV_STATUS_CLIENT_QUIT, ///< The connection is lost gracefully. Other clients are already informed of this leaving client.
NETWORK_RECV_STATUS_MALFORMED_PACKET, ///< We apparently send a malformed packet.
NETWORK_RECV_STATUS_SERVER_ERROR, ///< The server told us we made an error.
NETWORK_RECV_STATUS_SERVER_FULL, ///< The server is full.
NETWORK_RECV_STATUS_SERVER_BANNED, ///< The server has banned us.
NETWORK_RECV_STATUS_CLOSE_QUERY, ///< Done querying the server.
NETWORK_RECV_STATUS_CONNECTION_LOST, ///< The connection is lost unexpectedly.
};
/** Forward declaration due to circular dependencies */
@@ -45,10 +46,7 @@ public:
NetworkSocketHandler() { this->has_quit = false; }
/** Close the socket when destructing the socket handler */
virtual ~NetworkSocketHandler() { this->Close(); }
/** Really close the socket */
virtual void Close() {}
virtual ~NetworkSocketHandler() {}
/**
* Close the current connection; for TCP this will be mostly equivalent

View File

@@ -52,7 +52,7 @@ const char *GetNetworkRevisionString()
/* Tag names are not mangled further. */
if (_openttd_revision_tagged) {
DEBUG(net, 1, "Network revision name is '%s'", network_revision);
DEBUG(net, 3, "Network revision name: %s", network_revision);
return network_revision;
}
@@ -72,7 +72,7 @@ const char *GetNetworkRevisionString()
/* Replace the git hash in revision string. */
strecpy(network_revision + hashofs, githash_suffix, network_revision + NETWORK_REVISION_LENGTH);
assert(strlen(network_revision) < NETWORK_REVISION_LENGTH); // strlen does not include terminator, constant does, hence strictly less than
DEBUG(net, 1, "Network revision name is '%s'", network_revision);
DEBUG(net, 3, "Network revision name: %s", network_revision);
}
return network_revision;

View File

@@ -39,14 +39,14 @@ static void NetworkFindBroadcastIPsInternal(NetworkAddressList *broadcast) // BE
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0) {
DEBUG(net, 0, "[core] error creating socket");
DEBUG(net, 0, "Could not create socket: %s", NetworkError::GetLast().AsString());
return;
}
char *output_pointer = nullptr;
int output_length = _netstat(sock, &output_pointer, 1);
if (output_length < 0) {
DEBUG(net, 0, "[core] error running _netstat");
DEBUG(net, 0, "Error running _netstat()");
return;
}
@@ -205,6 +205,6 @@ void NetworkFindBroadcastIPs(NetworkAddressList *broadcast)
int i = 0;
for (NetworkAddress &addr : *broadcast) {
addr.SetPort(NETWORK_DEFAULT_PORT);
DEBUG(net, 3, "%d) %s", i++, addr.GetHostname());
DEBUG(net, 3, " %d) %s", i++, addr.GetHostname());
}
}

View File

@@ -28,7 +28,8 @@ NetworkTCPSocketHandler::NetworkTCPSocketHandler(SOCKET s) :
NetworkTCPSocketHandler::~NetworkTCPSocketHandler()
{
this->CloseConnection();
/* Virtual functions get called statically in destructors, so make it explicit to remove any confusion. */
this->NetworkTCPSocketHandler::CloseConnection();
if (this->sock != INVALID_SOCKET) closesocket(this->sock);
this->sock = INVALID_SOCKET;
@@ -118,7 +119,7 @@ SendPacketsState NetworkTCPSocketHandler::SendPackets(bool closing_down)
if (!err.WouldBlock()) {
/* Something went wrong.. close client! */
if (!closing_down) {
DEBUG(net, 0, "send failed with error %s", err.AsString());
DEBUG(net, 0, "Send failed: %s", err.AsString());
this->CloseConnection();
}
return SPS_CLOSED;
@@ -168,7 +169,7 @@ std::unique_ptr<Packet> NetworkTCPSocketHandler::ReceivePacket()
NetworkError err = NetworkError::GetLast();
if (!err.WouldBlock()) {
/* Something went wrong... */
if (!err.IsConnectionReset()) DEBUG(net, 0, "recv failed with error %s", err.AsString());
if (!err.IsConnectionReset()) DEBUG(net, 0, "Recv failed: %s", err.AsString());
this->CloseConnection();
return nullptr;
}
@@ -197,7 +198,7 @@ std::unique_ptr<Packet> NetworkTCPSocketHandler::ReceivePacket()
NetworkError err = NetworkError::GetLast();
if (!err.WouldBlock()) {
/* Something went wrong... */
if (!err.IsConnectionReset()) DEBUG(net, 0, "recv failed with error %s", err.AsString());
if (!err.IsConnectionReset()) DEBUG(net, 0, "Recv failed: %s", err.AsString());
this->CloseConnection();
return nullptr;
}

View File

@@ -15,9 +15,12 @@
#include "address.h"
#include "packet.h"
#include <deque>
#include <memory>
#include <atomic>
#include <chrono>
#include <deque>
#include <map>
#include <memory>
#include <thread>
/** The states of sending the packets. */
enum SendPacketsState {
@@ -73,23 +76,44 @@ public:
*/
class TCPConnecter {
private:
std::atomic<bool> connected;///< Whether we succeeded in making the connection
std::atomic<bool> aborted; ///< Whether we bailed out (i.e. connection making failed)
bool killed; ///< Whether we got killed
SOCKET sock; ///< The socket we're connecting with
/**
* The current status of the connecter.
*
* We track the status like this to ensure everything is executed from the
* game-thread, and not at another random time where we might not have the
* lock on the game-state.
*/
enum class Status {
INIT, ///< TCPConnecter is created but resolving hasn't started.
RESOLVING, ///< The hostname is being resolved (threaded).
FAILURE, ///< Resolving failed.
CONNECTING, ///< We are currently connecting.
};
void Connect();
std::thread resolve_thread; ///< Thread used during resolving.
std::atomic<Status> status = Status::INIT; ///< The current status of the connecter.
static void ThreadEntry(TCPConnecter *param);
addrinfo *ai = nullptr; ///< getaddrinfo() allocated linked-list of resolved addresses.
std::vector<addrinfo *> addresses; ///< Addresses we can connect to.
std::map<SOCKET, NetworkAddress> sock_to_address; ///< Mapping of a socket to the real address it is connecting to. USed for DEBUG statements.
size_t current_address = 0; ///< Current index in addresses we are trying.
protected:
/** Address we're connecting to */
NetworkAddress address;
std::vector<SOCKET> sockets; ///< Pending connect() attempts.
std::chrono::steady_clock::time_point last_attempt; ///< Time we last tried to connect.
std::string connection_string; ///< Current address we are connecting to (before resolving).
void Resolve();
void OnResolved(addrinfo *ai);
bool TryNextAddress();
void Connect(addrinfo *address);
bool CheckActivity();
static void ResolveThunk(TCPConnecter *connecter);
public:
TCPConnecter(const std::string &connection_string, uint16 default_port);
/** Silence the warnings */
virtual ~TCPConnecter() {}
virtual ~TCPConnecter();
/**
* Callback when the connection succeeded.

View File

@@ -41,7 +41,7 @@ NetworkAdminSocketHandler::~NetworkAdminSocketHandler()
NetworkRecvStatus NetworkAdminSocketHandler::CloseConnection(bool error)
{
delete this;
return NETWORK_RECV_STATUS_CONN_LOST;
return NETWORK_RECV_STATUS_CLIENT_QUIT;
}
/**
@@ -93,9 +93,9 @@ NetworkRecvStatus NetworkAdminSocketHandler::HandlePacket(Packet *p)
default:
if (this->HasClientQuit()) {
DEBUG(net, 0, "[tcp/admin] received invalid packet type %d from '%s' (%s)", type, this->admin_name, this->admin_version);
DEBUG(net, 0, "[tcp/admin] Received invalid packet type %d from '%s' (%s)", type, this->admin_name, this->admin_version);
} else {
DEBUG(net, 0, "[tcp/admin] received illegal packet from '%s' (%s)", this->admin_name, this->admin_version);
DEBUG(net, 0, "[tcp/admin] Received illegal packet from '%s' (%s)", this->admin_name, this->admin_version);
}
this->CloseConnection();
@@ -128,7 +128,7 @@ NetworkRecvStatus NetworkAdminSocketHandler::ReceivePackets()
*/
NetworkRecvStatus NetworkAdminSocketHandler::ReceiveInvalidPacket(PacketAdminType type)
{
DEBUG(net, 0, "[tcp/admin] received illegal packet type %d from admin %s (%s)", type, this->admin_name, this->admin_version);
DEBUG(net, 0, "[tcp/admin] Received illegal packet type %d from admin %s (%s)", type, this->admin_name, this->admin_version);
return NETWORK_RECV_STATUS_MALFORMED_PACKET;
}

View File

@@ -15,6 +15,8 @@
#include "tcp.h"
#include "../network_internal.h"
#include <deque>
#include "../../safeguards.h"
/** List of connections that are currently being created */
@@ -24,38 +26,325 @@ static std::vector<TCPConnecter *> _tcp_connecters;
* Create a new connecter for the given address
* @param connection_string the address to connect to
*/
TCPConnecter::TCPConnecter(const std::string &connection_string, uint16 default_port) :
connected(false),
aborted(false),
killed(false),
sock(INVALID_SOCKET)
TCPConnecter::TCPConnecter(const std::string &connection_string, uint16 default_port)
{
this->address = ParseConnectionString(connection_string, default_port);
this->connection_string = NormalizeConnectionString(connection_string, default_port);
_tcp_connecters.push_back(this);
if (!StartNewThread(nullptr, "ottd:tcp", &TCPConnecter::ThreadEntry, this)) {
this->Connect();
}
}
/** The actual connection function */
void TCPConnecter::Connect()
TCPConnecter::~TCPConnecter()
{
this->sock = this->address.Connect();
if (this->sock == INVALID_SOCKET) {
this->aborted = true;
} else {
this->connected = true;
if (this->resolve_thread.joinable()) {
this->resolve_thread.join();
}
for (const auto &socket : this->sockets) {
closesocket(socket);
}
this->sockets.clear();
this->sock_to_address.clear();
freeaddrinfo(this->ai);
}
/**
* Entry point for the new threads.
* @param param the TCPConnecter instance to call Connect on.
* Start a connection to the indicated address.
* @param address The address to connection to.
*/
/* static */ void TCPConnecter::ThreadEntry(TCPConnecter *param)
void TCPConnecter::Connect(addrinfo *address)
{
param->Connect();
SOCKET sock = socket(address->ai_family, address->ai_socktype, address->ai_protocol);
if (sock == INVALID_SOCKET) {
DEBUG(net, 0, "Could not create %s %s socket: %s", NetworkAddress::SocketTypeAsString(address->ai_socktype), NetworkAddress::AddressFamilyAsString(address->ai_family), NetworkError::GetLast().AsString());
return;
}
if (!SetNoDelay(sock)) {
DEBUG(net, 1, "Setting TCP_NODELAY failed: %s", NetworkError::GetLast().AsString());
}
if (!SetNonBlocking(sock)) {
DEBUG(net, 0, "Setting non-blocking mode failed: %s", NetworkError::GetLast().AsString());
}
NetworkAddress network_address = NetworkAddress(address->ai_addr, (int)address->ai_addrlen);
DEBUG(net, 5, "Attempting to connect to %s", network_address.GetAddressAsString().c_str());
int err = connect(sock, address->ai_addr, (int)address->ai_addrlen);
if (err != 0 && !NetworkError::GetLast().IsConnectInProgress()) {
closesocket(sock);
DEBUG(net, 1, "Could not connect to %s: %s", network_address.GetAddressAsString().c_str(), NetworkError::GetLast().AsString());
return;
}
this->sock_to_address[sock] = network_address;
this->sockets.push_back(sock);
}
/**
* Start the connect() for the next address in the list.
* @return True iff a new connect() is attempted.
*/
bool TCPConnecter::TryNextAddress()
{
if (this->current_address >= this->addresses.size()) return false;
this->last_attempt = std::chrono::steady_clock::now();
this->Connect(this->addresses[this->current_address++]);
return true;
}
/**
* Callback when resolving is done.
* @param ai A linked-list of address information.
*/
void TCPConnecter::OnResolved(addrinfo *ai)
{
std::deque<addrinfo *> addresses_ipv4, addresses_ipv6;
/* Apply "Happy Eyeballs" if it is likely IPv6 is functional. */
/* Detect if IPv6 is likely to succeed or not. */
bool seen_ipv6 = false;
bool resort = true;
for (addrinfo *runp = ai; runp != nullptr; runp = runp->ai_next) {
if (runp->ai_family == AF_INET6) {
seen_ipv6 = true;
} else if (!seen_ipv6) {
/* We see an IPv4 before an IPv6; this most likely means there is
* no IPv6 available on the system, so keep the order of this
* list. */
resort = false;
break;
}
}
/* Convert the addrinfo into NetworkAddresses. */
for (addrinfo *runp = ai; runp != nullptr; runp = runp->ai_next) {
if (resort) {
if (runp->ai_family == AF_INET6) {
addresses_ipv6.emplace_back(runp);
} else {
addresses_ipv4.emplace_back(runp);
}
} else {
this->addresses.emplace_back(runp);
}
}
/* If we want to resort, make the list like IPv6 / IPv4 / IPv6 / IPv4 / ..
* for how ever many (round-robin) DNS entries we have. */
if (resort) {
while (!addresses_ipv4.empty() || !addresses_ipv6.empty()) {
if (!addresses_ipv6.empty()) {
this->addresses.push_back(addresses_ipv6.front());
addresses_ipv6.pop_front();
}
if (!addresses_ipv4.empty()) {
this->addresses.push_back(addresses_ipv4.front());
addresses_ipv4.pop_front();
}
}
}
if (_debug_net_level >= 6) {
DEBUG(net, 6, "%s resolved in:", this->connection_string.c_str());
for (const auto &address : this->addresses) {
DEBUG(net, 6, "- %s", NetworkAddress(address->ai_addr, (int)address->ai_addrlen).GetAddressAsString().c_str());
}
}
this->current_address = 0;
}
/**
* Start resolving the hostname.
*
* This function must change "status" to either Status::FAILURE
* or Status::CONNECTING before returning.
*/
void TCPConnecter::Resolve()
{
/* Port is already guaranteed part of the connection_string. */
NetworkAddress address = ParseConnectionString(this->connection_string, 0);
addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_flags = AI_ADDRCONFIG;
hints.ai_socktype = SOCK_STREAM;
char port_name[6];
seprintf(port_name, lastof(port_name), "%u", address.GetPort());
static bool getaddrinfo_timeout_error_shown = false;
auto start = std::chrono::steady_clock::now();
addrinfo *ai;
int error = getaddrinfo(address.GetHostname(), port_name, &hints, &ai);
auto end = std::chrono::steady_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::seconds>(end - start);
if (!getaddrinfo_timeout_error_shown && duration >= std::chrono::seconds(5)) {
DEBUG(net, 0, "getaddrinfo() for address \"%s\" took %i seconds", this->connection_string.c_str(), (int)duration.count());
DEBUG(net, 0, " This is likely an issue in the DNS name resolver's configuration causing it to time out");
getaddrinfo_timeout_error_shown = true;
}
if (error != 0) {
DEBUG(net, 0, "Failed to resolve DNS for %s", this->connection_string.c_str());
this->status = Status::FAILURE;
return;
}
this->ai = ai;
this->OnResolved(ai);
this->status = Status::CONNECTING;
}
/**
* Thunk to start Resolve() on the right instance.
*/
/* static */ void TCPConnecter::ResolveThunk(TCPConnecter *connecter)
{
connecter->Resolve();
}
/**
* Check if there was activity for this connecter.
* @return True iff the TCPConnecter is done and can be cleaned up.
*/
bool TCPConnecter::CheckActivity()
{
switch (this->status.load()) {
case Status::INIT:
/* Start the thread delayed, so the vtable is loaded. This allows classes
* to overload functions used by Resolve() (in case threading is disabled). */
if (StartNewThread(&this->resolve_thread, "ottd:resolve", &TCPConnecter::ResolveThunk, this)) {
this->status = Status::RESOLVING;
return false;
}
/* No threads, do a blocking resolve. */
this->Resolve();
/* Continue as we are either failed or can start the first
* connection. The rest of this function handles exactly that. */
break;
case Status::RESOLVING:
/* Wait till Resolve() comes back with an answer (in case it runs threaded). */
return false;
case Status::FAILURE:
/* Ensure the OnFailure() is called from the game-thread instead of the
* resolve-thread, as otherwise we can get into some threading issues. */
this->OnFailure();
return true;
case Status::CONNECTING:
break;
}
/* If there are no attempts pending, connect to the next. */
if (this->sockets.empty()) {
if (!this->TryNextAddress()) {
/* There were no more addresses to try, so we failed. */
this->OnFailure();
return true;
}
return false;
}
fd_set write_fd;
FD_ZERO(&write_fd);
for (const auto &socket : this->sockets) {
FD_SET(socket, &write_fd);
}
timeval tv;
tv.tv_usec = 0;
tv.tv_sec = 0;
int n = select(FD_SETSIZE, NULL, &write_fd, NULL, &tv);
/* select() failed; hopefully next try it doesn't. */
if (n < 0) {
/* select() normally never fails; so hopefully it works next try! */
DEBUG(net, 1, "select() failed: %s", NetworkError::GetLast().AsString());
return false;
}
/* No socket updates. */
if (n == 0) {
/* Wait 250ms between attempting another address. */
if (std::chrono::steady_clock::now() < this->last_attempt + std::chrono::milliseconds(250)) return false;
/* Try the next address in the list. */
if (this->TryNextAddress()) return false;
/* Wait up to 3 seconds since the last connection we started. */
if (std::chrono::steady_clock::now() < this->last_attempt + std::chrono::milliseconds(3000)) return false;
/* More than 3 seconds no socket reported activity, and there are no
* more address to try. Timeout the attempt. */
DEBUG(net, 0, "Timeout while connecting to %s", this->connection_string.c_str());
for (const auto &socket : this->sockets) {
closesocket(socket);
}
this->sockets.clear();
this->sock_to_address.clear();
this->OnFailure();
return true;
}
/* Check for errors on any of the sockets. */
for (auto it = this->sockets.begin(); it != this->sockets.end(); /* nothing */) {
NetworkError socket_error = GetSocketError(*it);
if (socket_error.HasError()) {
DEBUG(net, 1, "Could not connect to %s: %s", this->sock_to_address[*it].GetAddressAsString().c_str(), socket_error.AsString());
closesocket(*it);
this->sock_to_address.erase(*it);
it = this->sockets.erase(it);
} else {
it++;
}
}
/* In case all sockets had an error, queue a new one. */
if (this->sockets.empty()) {
if (!this->TryNextAddress()) {
/* There were no more addresses to try, so we failed. */
this->OnFailure();
return true;
}
return false;
}
/* At least one socket is connected. The first one that does is the one
* we will be using, and we close all other sockets. */
SOCKET connected_socket = INVALID_SOCKET;
for (auto it = this->sockets.begin(); it != this->sockets.end(); /* nothing */) {
if (connected_socket == INVALID_SOCKET && FD_ISSET(*it, &write_fd)) {
connected_socket = *it;
} else {
closesocket(*it);
}
this->sock_to_address.erase(*it);
it = this->sockets.erase(it);
}
assert(connected_socket != INVALID_SOCKET);
DEBUG(net, 3, "Connected to %s", this->connection_string.c_str());
if (_debug_net_level >= 5) {
DEBUG(net, 5, "- using %s", NetworkAddress::GetPeerName(connected_socket).c_str());
}
this->OnConnect(connected_socket);
return true;
}
/**
@@ -68,32 +357,22 @@ void TCPConnecter::Connect()
{
for (auto iter = _tcp_connecters.begin(); iter < _tcp_connecters.end(); /* nothing */) {
TCPConnecter *cur = *iter;
const bool connected = cur->connected.load();
const bool aborted = cur->aborted.load();
if ((connected || aborted) && cur->killed) {
if (cur->CheckActivity()) {
iter = _tcp_connecters.erase(iter);
if (cur->sock != INVALID_SOCKET) closesocket(cur->sock);
delete cur;
continue;
} else {
iter++;
}
if (connected) {
iter = _tcp_connecters.erase(iter);
cur->OnConnect(cur->sock);
delete cur;
continue;
}
if (aborted) {
iter = _tcp_connecters.erase(iter);
cur->OnFailure();
delete cur;
continue;
}
iter++;
}
}
/** Kill all connection attempts. */
/* static */ void TCPConnecter::KillAll()
{
for (TCPConnecter *conn : _tcp_connecters) conn->killed = true;
for (auto iter = _tcp_connecters.begin(); iter < _tcp_connecters.end(); /* nothing */) {
TCPConnecter *cur = *iter;
iter = _tcp_connecters.erase(iter);
delete cur;
}
}

View File

@@ -137,9 +137,11 @@ const char *ContentInfo::GetTextfile(TextfileType type) const
return ::GetTextfile(type, GetContentInfoSubDir(this->type), tmp);
}
void NetworkContentSocketHandler::Close()
/**
* Close the actual socket.
*/
void NetworkContentSocketHandler::CloseSocket()
{
CloseConnection();
if (this->sock == INVALID_SOCKET) return;
closesocket(this->sock);
@@ -167,9 +169,9 @@ bool NetworkContentSocketHandler::HandlePacket(Packet *p)
default:
if (this->HasClientQuit()) {
DEBUG(net, 0, "[tcp/content] received invalid packet type %d", type);
DEBUG(net, 0, "[tcp/content] Received invalid packet type %d", type);
} else {
DEBUG(net, 0, "[tcp/content] received illegal packet");
DEBUG(net, 0, "[tcp/content] Received illegal packet");
}
return false;
}
@@ -219,7 +221,7 @@ bool NetworkContentSocketHandler::ReceivePackets()
*/
bool NetworkContentSocketHandler::ReceiveInvalidPacket(PacketContentType type)
{
DEBUG(net, 0, "[tcp/content] received illegal packet type %d", type);
DEBUG(net, 0, "[tcp/content] Received illegal packet type %d", type);
return false;
}

View File

@@ -21,7 +21,7 @@
/** Base socket handler for all Content TCP sockets */
class NetworkContentSocketHandler : public NetworkTCPSocketHandler {
protected:
void Close() override;
void CloseSocket();
bool ReceiveInvalidPacket(PacketContentType type);
@@ -128,7 +128,11 @@ public:
}
/** On destructing of this class, the socket needs to be closed */
virtual ~NetworkContentSocketHandler() { this->Close(); }
virtual ~NetworkContentSocketHandler()
{
/* Virtual functions get called statically in destructors, so make it explicit to remove any confusion. */
this->CloseSocket();
}
bool ReceivePackets();
};

View File

@@ -102,7 +102,7 @@ NetworkGameSocketHandler::NetworkGameSocketHandler(SOCKET s) : info(nullptr), cl
*/
NetworkRecvStatus NetworkGameSocketHandler::CloseConnection(bool error)
{
if (this->ignore_close) return NETWORK_RECV_STATUS_CONN_LOST;
if (this->ignore_close) return NETWORK_RECV_STATUS_CLIENT_QUIT;
/* Clients drop back to the main menu */
if (!_network_server && _networking) {
@@ -112,10 +112,10 @@ NetworkRecvStatus NetworkGameSocketHandler::CloseConnection(bool error)
_networking = false;
ShowErrorMessage(STR_NETWORK_ERROR_LOSTCONNECTION, INVALID_STRING_ID, WL_CRITICAL);
return NETWORK_RECV_STATUS_CONN_LOST;
return NETWORK_RECV_STATUS_CLIENT_QUIT;
}
return this->CloseConnection(error ? NETWORK_RECV_STATUS_SERVER_ERROR : NETWORK_RECV_STATUS_CONN_LOST);
return this->CloseConnection(NETWORK_RECV_STATUS_CONNECTION_LOST);
}
@@ -190,9 +190,9 @@ NetworkRecvStatus NetworkGameSocketHandler::HandlePacket(Packet *p)
this->CloseConnection();
if (this->HasClientQuit()) {
DEBUG(net, 0, "[tcp/game] received invalid packet type %d from client %d", type, this->client_id);
DEBUG(net, 0, "[tcp/game] Received invalid packet type %d from client %d", type, this->client_id);
} else {
DEBUG(net, 0, "[tcp/game] received illegal packet from client %d", this->client_id);
DEBUG(net, 0, "[tcp/game] Received illegal packet from client %d", this->client_id);
}
return NETWORK_RECV_STATUS_MALFORMED_PACKET;
}
@@ -223,7 +223,7 @@ NetworkRecvStatus NetworkGameSocketHandler::ReceivePackets()
*/
NetworkRecvStatus NetworkGameSocketHandler::ReceiveInvalidPacket(PacketGameType type)
{
DEBUG(net, 0, "[tcp/game] received illegal packet type %d from client %d", type, this->client_id);
DEBUG(net, 0, "[tcp/game] Received illegal packet type %d from client %d", type, this->client_id);
return NETWORK_RECV_STATUS_MALFORMED_PACKET;
}

View File

@@ -45,7 +45,7 @@ NetworkHTTPSocketHandler::NetworkHTTPSocketHandler(SOCKET s,
size_t bufferSize = strlen(url) + strlen(host) + strlen(_openttd_revision) + (data == nullptr ? 0 : strlen(data)) + 128;
char *buffer = AllocaM(char, bufferSize);
DEBUG(net, 7, "[tcp/http] requesting %s%s", host, url);
DEBUG(net, 5, "[tcp/http] Requesting %s%s", host, url);
if (data != nullptr) {
seprintf(buffer, buffer + bufferSize - 1, "POST %s HTTP/1.0\r\nHost: %s\r\nUser-Agent: OpenTTD/%s\r\nContent-Type: text/plain\r\nContent-Length: %d\r\n\r\n%s\r\n", url, host, _openttd_revision, (int)strlen(data), data);
} else {
@@ -85,7 +85,7 @@ NetworkRecvStatus NetworkHTTPSocketHandler::CloseConnection(bool error)
* Helper to simplify the error handling.
* @param msg the error message to show.
*/
#define return_error(msg) { DEBUG(net, 0, msg); return -1; }
#define return_error(msg) { DEBUG(net, 1, msg); return -1; }
static const char * const NEWLINE = "\r\n"; ///< End of line marker
static const char * const END_OF_HEADER = "\r\n\r\n"; ///< End of header marker
@@ -112,7 +112,7 @@ int NetworkHTTPSocketHandler::HandleHeader()
/* We expect a HTTP/1.[01] reply */
if (strncmp(this->recv_buffer, HTTP_1_0, strlen(HTTP_1_0)) != 0 &&
strncmp(this->recv_buffer, HTTP_1_1, strlen(HTTP_1_1)) != 0) {
return_error("[tcp/http] received invalid HTTP reply");
return_error("[tcp/http] Received invalid HTTP reply");
}
char *status = this->recv_buffer + strlen(HTTP_1_0);
@@ -121,7 +121,7 @@ int NetworkHTTPSocketHandler::HandleHeader()
/* Get the length of the document to receive */
char *length = strcasestr(this->recv_buffer, CONTENT_LENGTH);
if (length == nullptr) return_error("[tcp/http] missing 'content-length' header");
if (length == nullptr) return_error("[tcp/http] Missing 'content-length' header");
/* Skip the header */
length += strlen(CONTENT_LENGTH);
@@ -139,9 +139,9 @@ int NetworkHTTPSocketHandler::HandleHeader()
/* Make sure we're going to download at least something;
* zero sized files are, for OpenTTD's purposes, always
* wrong. You can't have gzips of 0 bytes! */
if (len == 0) return_error("[tcp/http] refusing to download 0 bytes");
if (len == 0) return_error("[tcp/http] Refusing to download 0 bytes");
DEBUG(net, 7, "[tcp/http] downloading %i bytes", len);
DEBUG(net, 7, "[tcp/http] Downloading %i bytes", len);
return len;
}
@@ -154,15 +154,15 @@ int NetworkHTTPSocketHandler::HandleHeader()
/* Search the end of the line. This is safe because the header will
* always end with two newlines. */
*strstr(status, NEWLINE) = '\0';
DEBUG(net, 0, "[tcp/http] unhandled status reply %s", status);
DEBUG(net, 1, "[tcp/http] Unhandled status reply %s", status);
return -1;
}
if (this->redirect_depth == 5) return_error("[tcp/http] too many redirects, looping redirects?");
if (this->redirect_depth == 5) return_error("[tcp/http] Too many redirects, looping redirects?");
/* Redirect to other URL */
char *uri = strcasestr(this->recv_buffer, LOCATION);
if (uri == nullptr) return_error("[tcp/http] missing 'location' header for redirect");
if (uri == nullptr) return_error("[tcp/http] Missing 'location' header for redirect");
uri += strlen(LOCATION);
@@ -171,7 +171,7 @@ int NetworkHTTPSocketHandler::HandleHeader()
char *end_of_line = strstr(uri, NEWLINE);
*end_of_line = '\0';
DEBUG(net, 6, "[tcp/http] redirecting to %s", uri);
DEBUG(net, 7, "[tcp/http] Redirecting to %s", uri);
int ret = NetworkHTTPSocketHandler::Connect(uri, this->callback, this->data, this->redirect_depth + 1);
if (ret != 0) return ret;
@@ -194,18 +194,20 @@ int NetworkHTTPSocketHandler::HandleHeader()
/* static */ int NetworkHTTPSocketHandler::Connect(char *uri, HTTPCallback *callback, const char *data, int depth)
{
char *hname = strstr(uri, "://");
if (hname == nullptr) return_error("[tcp/http] invalid location");
if (hname == nullptr) return_error("[tcp/http] Invalid location");
hname += 3;
char *url = strchr(hname, '/');
if (url == nullptr) return_error("[tcp/http] invalid location");
if (url == nullptr) return_error("[tcp/http] Invalid location");
*url = '\0';
std::string hostname = std::string(hname);
/* Restore the URL. */
*url = '/';
new NetworkHTTPContentConnecter(hname, callback, url, data, depth);
new NetworkHTTPContentConnecter(hostname, callback, url, data, depth);
return 0;
}
@@ -226,7 +228,7 @@ int NetworkHTTPSocketHandler::Receive()
NetworkError err = NetworkError::GetLast();
if (!err.WouldBlock()) {
/* Something went wrong... */
if (!err.IsConnectionReset()) DEBUG(net, 0, "recv failed with error %s", err.AsString());
if (!err.IsConnectionReset()) DEBUG(net, 0, "Recv failed: %s", err.AsString());
return -1;
}
/* Connection would block, so stop for now */
@@ -254,7 +256,7 @@ int NetworkHTTPSocketHandler::Receive()
if (end_of_header == nullptr) {
if (read == lengthof(this->recv_buffer)) {
DEBUG(net, 0, "[tcp/http] header too big");
DEBUG(net, 1, "[tcp/http] Header too big");
return -1;
}
this->recv_pos = read;

View File

@@ -73,6 +73,7 @@ public:
/** Connect with a HTTP server and do ONE query. */
class NetworkHTTPContentConnecter : TCPConnecter {
std::string hostname; ///< Hostname we are connecting to.
HTTPCallback *callback; ///< Callback to tell that we received some data (or won't).
const char *url; ///< The URL we want to get at the server.
const char *data; ///< The data to send
@@ -81,14 +82,15 @@ class NetworkHTTPContentConnecter : TCPConnecter {
public:
/**
* Start the connecting.
* @param connection_string The address to connect to.
* @param hostname The hostname to connect to.
* @param callback The callback for HTTP retrieval.
* @param url The url at the server.
* @param data The data to send.
* @param depth The depth (redirect recursion) of the queries.
*/
NetworkHTTPContentConnecter(const std::string &connection_string, HTTPCallback *callback, const char *url, const char *data = nullptr, int depth = 0) :
TCPConnecter(connection_string, 80),
NetworkHTTPContentConnecter(const std::string &hostname, HTTPCallback *callback, const char *url, const char *data = nullptr, int depth = 0) :
TCPConnecter(hostname, 80),
hostname(hostname),
callback(callback),
url(stredup(url)),
data(data),
@@ -110,7 +112,7 @@ public:
void OnConnect(SOCKET s) override
{
new NetworkHTTPSocketHandler(s, this->callback, this->address.GetHostname(), this->url, this->data, this->depth);
new NetworkHTTPSocketHandler(s, this->callback, this->hostname.c_str(), this->url, this->data, this->depth);
/* We've relinquished control of data now. */
this->data = nullptr;
}

View File

@@ -49,7 +49,7 @@ public:
SetNonBlocking(s); // XXX error handling?
NetworkAddress address(sin, sin_len);
DEBUG(net, 1, "[%s] Client connected from %s on frame %d", Tsocket::GetName(), address.GetHostname(), _frame_counter);
DEBUG(net, 3, "[%s] Client connected from %s on frame %d", Tsocket::GetName(), address.GetHostname(), _frame_counter);
SetNoDelay(s); // XXX error handling?
@@ -61,10 +61,10 @@ public:
Packet p(Tban_packet);
p.PrepareToSend();
DEBUG(net, 1, "[%s] Banned ip tried to join (%s), refused", Tsocket::GetName(), entry.c_str());
DEBUG(net, 2, "[%s] Banned ip tried to join (%s), refused", Tsocket::GetName(), entry.c_str());
if (p.TransferOut<int>(send, s, 0) < 0) {
DEBUG(net, 0, "send failed with error %s", NetworkError::GetLast().AsString());
DEBUG(net, 0, "[%s] send failed: %s", Tsocket::GetName(), NetworkError::GetLast().AsString());
}
closesocket(s);
break;
@@ -81,7 +81,7 @@ public:
p.PrepareToSend();
if (p.TransferOut<int>(send, s, 0) < 0) {
DEBUG(net, 0, "send failed with error %s", NetworkError::GetLast().AsString());
DEBUG(net, 0, "[%s] send failed: %s", Tsocket::GetName(), NetworkError::GetLast().AsString());
}
closesocket(s);
@@ -150,7 +150,7 @@ public:
}
if (sockets.size() == 0) {
DEBUG(net, 0, "[server] could not start network: could not create listening socket");
DEBUG(net, 0, "Could not start network: could not create listening socket");
ShowNetworkError(STR_NETWORK_ERROR_SERVER_START);
return false;
}
@@ -165,7 +165,7 @@ public:
closesocket(s.second);
}
sockets.clear();
DEBUG(net, 1, "[%s] closed listeners", Tsocket::GetName());
DEBUG(net, 5, "[%s] Closed listeners", Tsocket::GetName());
}
};

View File

@@ -127,16 +127,16 @@ void NetworkUDPSocketHandler::SendPacket(Packet *p, NetworkAddress *recv, bool a
/* Enable broadcast */
unsigned long val = 1;
if (setsockopt(s.second, SOL_SOCKET, SO_BROADCAST, (char *) &val, sizeof(val)) < 0) {
DEBUG(net, 1, "[udp] setting broadcast failed with: %s", NetworkError::GetLast().AsString());
DEBUG(net, 1, "Setting broadcast mode failed: %s", NetworkError::GetLast().AsString());
}
}
/* Send the buffer */
ssize_t res = p->TransferOut<int>(sendto, s.second, 0, (const struct sockaddr *)send.GetAddress(), send.GetAddressLength());
DEBUG(net, 7, "[udp] sendto(%s)", NetworkAddressDumper().GetAddressAsString(&send));
DEBUG(net, 7, "sendto(%s)", NetworkAddressDumper().GetAddressAsString(&send));
/* Check for any errors, but ignore it otherwise */
if (res == -1) DEBUG(net, 1, "[udp] sendto(%s) failed with: %s", NetworkAddressDumper().GetAddressAsString(&send), NetworkError::GetLast().AsString());
if (res == -1) DEBUG(net, 1, "sendto(%s) failed with: %s", NetworkAddressDumper().GetAddressAsString(&send), NetworkError::GetLast().AsString());
if (!all) break;
}

View File

@@ -211,7 +211,7 @@ public:
virtual ~NetworkUDPSocketHandler() { this->Close(); }
bool Listen();
void Close() override;
void Close();
void SendPacket(Packet *p, NetworkAddress *recv, bool all = false, bool broadcast = false, bool short_mtu = false);
void ReceivePackets();