From ec633f3bd80b7566c96bd6bc656814d0bcaae000 Mon Sep 17 00:00:00 2001 From: rubidium Date: Tue, 2 Jan 2007 17:34:03 +0000 Subject: (svn r7751) -Codechange: move network_* to a new network map. Furthermore move the low level network functions to network/core, so they can be reused by the masterserver and website-serverlist-updater. --- network/core/config.h | 49 ++++++++ network/core/game.h | 47 +++++++ network/core/os_abstraction.h | 181 +++++++++++++++++++++++++++ network/core/packet.c | 216 ++++++++++++++++++++++++++++++++ network/core/packet.h | 67 ++++++++++ network/core/tcp.c | 227 ++++++++++++++++++++++++++++++++++ network/core/tcp.h | 60 +++++++++ network/core/udp.c | 277 ++++++++++++++++++++++++++++++++++++++++++ network/core/udp.h | 62 ++++++++++ 9 files changed, 1186 insertions(+) create mode 100644 network/core/config.h create mode 100644 network/core/game.h create mode 100644 network/core/os_abstraction.h create mode 100644 network/core/packet.c create mode 100644 network/core/packet.h create mode 100644 network/core/tcp.c create mode 100644 network/core/tcp.h create mode 100644 network/core/udp.c create mode 100644 network/core/udp.h (limited to 'network/core') diff --git a/network/core/config.h b/network/core/config.h new file mode 100644 index 000000000..0b80800f0 --- /dev/null +++ b/network/core/config.h @@ -0,0 +1,49 @@ +/* $Id$ */ + +#ifndef NETWORK_CORE_CONFIG_H +#define NETWORK_CORE_CONFIG_H + +#ifdef ENABLE_NETWORK + +/** DNS hostname of the masterserver */ +#define NETWORK_MASTER_SERVER_HOST "master.openttd.org" +/** Message sent to the masterserver to 'identify' this client as OpenTTD */ +#define NETWORK_MASTER_SERVER_WELCOME_MESSAGE "OpenTTDRegister" + +enum { + NETWORK_MASTER_SERVER_PORT = 3978, ///< The default port of the master server (UDP) + NETWORK_DEFAULT_PORT = 3979, ///< The default port of the game server (TCP & UDP) + + SEND_MTU = 1460, ///< Number of bytes we can pack in a single packet + + NETWORK_GAME_INFO_VERSION = 4, ///< What version of game-info do we use? + NETWORK_COMPANY_INFO_VERSION = 4, ///< What version of company info is this? + NETWORK_MASTER_SERVER_VERSION = 1, ///< What version of master-server-protocol do we use? + + NETWORK_NAME_LENGTH = 80, ///< The maximum length of the server name and map name, in bytes including '\0' + NETWORK_HOSTNAME_LENGTH = 80, ///< The maximum length of the host name, in bytes including '\0' + NETWORK_REVISION_LENGTH = 15, ///< The maximum length of the revision, in bytes including '\0' + NETWORK_PASSWORD_LENGTH = 20, ///< The maximum length of the password, in bytes including '\0' + NETWORK_PLAYERS_LENGTH = 200, ///< The maximum length for the list of players that controls a company, in bytes including '\0' + NETWORK_CLIENT_NAME_LENGTH = 25, ///< The maximum length of a player, in bytes including '\0' + NETWORK_RCONCOMMAND_LENGTH = 500, ///< The maximum length of a rconsole command, in bytes including '\0' + + NETWORK_GRF_NAME_LENGTH = 80, ///< Maximum length of the name of a GRF + /** + * Maximum number of GRFs that can be sent. + * This value is related to number of handles (files) OpenTTD can open. + * This is currently 64 and about 10 are currently used when OpenTTD loads + * without any NewGRFs. Therefore one can only load about 55 NewGRFs, so + * this is not a limit, but rather a way to easily check whether the limit + * imposed by the handle count is reached. Secondly it isn't possible to + * send much more GRF IDs + MD5sums in the PACKET_UDP_SERVER_RESPONSE, due + * to the limited size of UDP packets. + */ + NETWORK_MAX_GRF_COUNT = 55, + + NETWORK_NUM_LANGUAGES = 4, ///< Number of known languages (to the network protocol) + 1 for 'any'. +}; + +#endif /* ENABLE_NETWORK */ + +#endif /* NETWORK_CORE_CONFIG_H */ diff --git a/network/core/game.h b/network/core/game.h new file mode 100644 index 000000000..71268f7d2 --- /dev/null +++ b/network/core/game.h @@ -0,0 +1,47 @@ +/* $Id$ */ + +#ifndef NETWORK_CORE_GAME_H +#define NETWORK_CORE_GAME_H + +#ifdef ENABLE_NETWORK + +/** + * @file game.h Information about a game that is sent between a + * game server, game client and masterserver. + */ + +/** + * This is the struct used by both client and server + * some fields will be empty on the client (like game_password) by default + * and only filled with data a player enters. + */ +typedef struct NetworkGameInfo { + byte game_info_version; ///< Version of the game info + char server_name[NETWORK_NAME_LENGTH]; ///< Server name + char hostname[NETWORK_HOSTNAME_LENGTH]; ///< Hostname of the server (if any) + char server_revision[NETWORK_REVISION_LENGTH]; ///< The version number the server is using (e.g.: 'r304' or 0.5.0) + bool version_compatible; ///< Can we connect to this server or not? (based on server_revision) + bool compatible; ///< Can we connect to this server or not? (based on server_revision _and_ grf_match + byte server_lang; ///< Language of the server (we should make a nice table for this) + byte use_password; ///< Is set to != 0 if it uses a password + char server_password[NETWORK_PASSWORD_LENGTH]; ///< On the server: the game password, on the client: != "" if server has password + byte clients_max; ///< Max clients allowed on server + byte clients_on; ///< Current count of clients on server + byte companies_max; ///< Max companies allowed on server + byte companies_on; ///< How many started companies do we have + byte spectators_max; ///< Max spectators allowed on server + byte spectators_on; ///< How many spectators do we have? + Date game_date; ///< Current date + Date start_date; ///< When the game started + char map_name[NETWORK_NAME_LENGTH]; ///< Map which is played ["random" for a randomized map] + uint16 map_width; ///< Map width + uint16 map_height; ///< Map height + byte map_set; ///< Graphical set + bool dedicated; ///< Is this a dedicated server? + char rcon_password[NETWORK_PASSWORD_LENGTH]; ///< RCon password for the server. "" if rcon is disabled + struct GRFConfig *grfconfig; ///< List of NewGRF files used +} NetworkGameInfo; + +#endif /* ENABLE_NETWORK */ + +#endif /* NETWORK_CORE_GAME_H */ diff --git a/network/core/os_abstraction.h b/network/core/os_abstraction.h new file mode 100644 index 000000000..c7df16a93 --- /dev/null +++ b/network/core/os_abstraction.h @@ -0,0 +1,181 @@ +/* $Id$ */ + +#ifndef NETWORK_CORE_OS_ABSTRACTION_H +#define NETWORK_CORE_OS_ABSTRACTION_H + +/** + * @file os_abstraction.h Network stuff has many things that needs to be + * included and/or implemented by default. + * All those things are in this file. + */ + +/* Include standard stuff per OS */ + +#ifdef ENABLE_NETWORK + +/* Windows stuff */ +#if defined(WIN32) || defined(WIN64) +#include +#include +#include + +#if !(defined(__MINGW32__) || defined(__CYGWIN__)) + /* Windows has some different names for some types */ + typedef SSIZE_T ssize_t; + typedef int socklen_t; +#endif + +#define GET_LAST_ERROR() WSAGetLastError() +#define EWOULDBLOCK WSAEWOULDBLOCK +/* Windows has some different names for some types */ +typedef unsigned long in_addr_t; +#endif /* WIN32 */ + +/* UNIX stuff */ +#if defined(UNIX) +# define SOCKET int +# define INVALID_SOCKET -1 +# if !defined(__MORPHOS__) && !defined(__AMIGA__) +# define ioctlsocket ioctl +# if !defined(BEOS_NET_SERVER) +# define closesocket close +# endif +# define GET_LAST_ERROR() (errno) +# endif +/* Need this for FIONREAD on solaris */ +# define BSD_COMP + +/* Includes needed for UNIX-like systems */ +# include +# include +# if defined(__BEOS__) && defined(BEOS_NET_SERVER) +# include +# include // snooze() +# include + typedef unsigned long in_addr_t; +# define INADDR_NONE INADDR_BROADCAST +# else +# include +# include +# include +# include +# include +/* According to glibc/NEWS, appeared in glibc-2.3. */ +# if !defined(__sgi__) && !defined(SUNOS) && !defined(__MORPHOS__) && !defined(__BEOS__) && !defined(__INNOTEK_LIBC__) \ + && !(defined(__GLIBC__) && (__GLIBC__ <= 2) && (__GLIBC_MINOR__ <= 2)) && !defined(__dietlibc__) +/* If for any reason ifaddrs.h does not exist on your system, comment out + * the following two lines and an alternative way will be used to fetch + * the list of IPs from the system. */ +# include +# define HAVE_GETIFADDRS +# endif +# if defined(SUNOS) || defined(__MORPHOS__) || defined(__BEOS__) +# define INADDR_NONE 0xffffffff +# endif +# if defined(__BEOS__) && !defined(BEOS_NET_SERVER) + /* needed on Zeta */ +# include +# endif +# endif /* BEOS_NET_SERVER */ + +# if !defined(__BEOS__) && defined(__GLIBC__) && (__GLIBC__ <= 2) && (__GLIBC_MINOR__ <= 1) + typedef uint32_t in_addr_t; +# endif + +# include +# include +# include +#endif // UNIX + +#ifdef __BEOS__ + typedef int socklen_t; +#endif + +/* OS/2 stuff */ +#if defined(__OS2__) +# define SOCKET int +# define INVALID_SOCKET -1 +# define ioctlsocket ioctl +# define closesocket close +# define GET_LAST_ERROR() (sock_errno()) + +/* Includes needed for OS/2 systems */ +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# define INADDR_NONE 0xffffffff + +typedef int socklen_t; +#if !defined(__INNOTEK_LIBC__) +typedef unsigned long in_addr_t; +#endif /* __INNOTEK_LIBC__ */ +#endif /* OS/2 */ + +/* MorphOS and Amiga stuff */ +#if defined(__MORPHOS__) || defined(__AMIGA__) +# include +# include // required for Open/CloseLibrary() +# if defined(__MORPHOS__) +# include // FIO* defines +# include // SIO* defines +# include +# else /* __AMIGA__ */ +# include +# endif + +/* Make the names compatible */ +# define closesocket(s) CloseSocket(s) +# define GET_LAST_ERROR() Errno() +# define ioctlsocket(s,request,status) IoctlSocket((LONG)s,(ULONG)request,(char*)status) +# define ioctl ioctlsocket + + typedef unsigned int in_addr_t; + typedef long socklen_t; + extern struct Library *SocketBase; + +# ifdef __AMIGA__ + /* for usleep() implementation */ + extern struct Device *TimerBase; + extern struct MsgPort *TimerPort; + extern struct timerequest *TimerRequest; +# endif +#endif // __MORPHOS__ || __AMIGA__ + +static inline bool SetNonBlocking(int d) +{ +#ifdef WIN32 + u_long nonblocking = 1; +#else + int nonblocking = 1; +#endif +#if defined(__BEOS__) && defined(BEOS_NET_SERVER) + return setsockopt(d, SOL_SOCKET, SO_NONBLOCK, &nonblocking, sizeof(nonblocking)) == 0; +#else + return ioctlsocket(d, FIONBIO, &nonblocking) == 0; +#endif +} + +static inline bool SetNoDelay(int d) +{ + /* XXX should this be done at all? */ +#if !defined(BEOS_NET_SERVER) // not implemented on BeOS net_server + int b = 1; + /* The (const char*) cast is needed for windows */ + return setsockopt(d, IPPROTO_TCP, TCP_NODELAY, (const char*)&b, sizeof(b)) == 0; +#else + return true; +#endif +} + +#endif /* ENABLE_NETWORK */ + +#endif /* NETWORK_CORE_OS_ABSTRACTION_H */ diff --git a/network/core/packet.c b/network/core/packet.c new file mode 100644 index 000000000..957bd2ad6 --- /dev/null +++ b/network/core/packet.c @@ -0,0 +1,216 @@ +/* $Id$ */ + +#ifdef ENABLE_NETWORK + +#include "../../stdafx.h" +#include "../../macros.h" +#include "../../string.h" + +#include "os_abstraction.h" +#include "config.h" +#include "packet.h" + +/** + * @file packet.h Basic functions to create, fill and read packets. + */ + + +/* Do not want to include functions.h and all required headers */ +extern void NORETURN CDECL error(const char *str, ...); + + +/** + * Create a packet for sending + * @param type the of packet + * @return the newly created packet + */ +Packet *NetworkSend_Init(PacketType type) +{ + Packet *packet = malloc(sizeof(Packet)); + /* An error is inplace here, because it simply means we ran out of memory. */ + if (packet == NULL) error("Failed to allocate Packet"); + + /* Skip the size so we can write that in before sending the packet */ + packet->size = sizeof(packet->size); + packet->buffer[packet->size++] = type; + packet->pos = 0; + + return packet; +} + +/** + * Writes the packet size from the raw packet from packet->size + * @param packet the packet to write the size of + */ +void NetworkSend_FillPacketSize(Packet *packet) +{ + packet->buffer[0] = GB(packet->size, 0, 8); + packet->buffer[1] = GB(packet->size, 8, 8); +} + +/** + * The next couple of functions make sure we can send + * uint8, uint16, uint32 and uint64 endian-safe + * over the network. The least significant bytes are + * sent first. + * + * So 0x01234567 would be sent as 67 45 23 01. + */ + +void NetworkSend_uint8(Packet *packet, uint8 data) +{ + assert(packet->size < sizeof(packet->buffer) - sizeof(data)); + packet->buffer[packet->size++] = data; +} + +void NetworkSend_uint16(Packet *packet, uint16 data) +{ + assert(packet->size < sizeof(packet->buffer) - sizeof(data)); + packet->buffer[packet->size++] = GB(data, 0, 8); + packet->buffer[packet->size++] = GB(data, 8, 8); +} + +void NetworkSend_uint32(Packet *packet, uint32 data) +{ + assert(packet->size < sizeof(packet->buffer) - sizeof(data)); + packet->buffer[packet->size++] = GB(data, 0, 8); + packet->buffer[packet->size++] = GB(data, 8, 8); + packet->buffer[packet->size++] = GB(data, 16, 8); + packet->buffer[packet->size++] = GB(data, 24, 8); +} + +void NetworkSend_uint64(Packet *packet, uint64 data) +{ + assert(packet->size < sizeof(packet->buffer) - sizeof(data)); + packet->buffer[packet->size++] = GB(data, 0, 8); + packet->buffer[packet->size++] = GB(data, 8, 8); + packet->buffer[packet->size++] = GB(data, 16, 8); + packet->buffer[packet->size++] = GB(data, 24, 8); + packet->buffer[packet->size++] = GB(data, 32, 8); + packet->buffer[packet->size++] = GB(data, 40, 8); + packet->buffer[packet->size++] = GB(data, 48, 8); + packet->buffer[packet->size++] = GB(data, 56, 8); +} + +/** + * Sends a string over the network. It sends out + * the string + '\0'. No size-byte or something. + */ +void NetworkSend_string(Packet *packet, const char* data) +{ + assert(data != NULL); + assert(packet->size < sizeof(packet->buffer) - strlen(data) - 1); + while ((packet->buffer[packet->size++] = *data++) != '\0') {} +} + + +/** + * Receiving commands + * Again, the next couple of functions are endian-safe + * see the comment before NetworkSend_uint8 for more info. + */ + + +extern uint CloseConnection(NetworkClientState *cs); + +/** Is it safe to read from the packet, i.e. didn't we run over the buffer ? */ +static inline bool CanReadFromPacket(NetworkClientState *cs, Packet *packet, uint bytes_to_read) +{ + /* Don't allow reading from a closed socket */ + if (HasClientQuit(cs)) return false; + + /* Check if variable is within packet-size */ + if (packet->pos + bytes_to_read > packet->size) { + CloseConnection(cs); + return false; + } + + return true; +} + +/** + * Reads the packet size from the raw packet and stores it in the packet->size + * @param packet the packet to read the size of + */ +void NetworkRecv_ReadPacketSize(Packet *packet) +{ + packet->size = (uint16)packet->buffer[0]; + packet->size += (uint16)packet->buffer[1] << 8; +} + +uint8 NetworkRecv_uint8(NetworkClientState *cs, Packet *packet) +{ + uint8 n; + + if (!CanReadFromPacket(cs, packet, sizeof(n))) return 0; + + n = packet->buffer[packet->pos++]; + return n; +} + +uint16 NetworkRecv_uint16(NetworkClientState *cs, Packet *packet) +{ + uint16 n; + + if (!CanReadFromPacket(cs, packet, sizeof(n))) return 0; + + n = (uint16)packet->buffer[packet->pos++]; + n += (uint16)packet->buffer[packet->pos++] << 8; + return n; +} + +uint32 NetworkRecv_uint32(NetworkClientState *cs, Packet *packet) +{ + uint32 n; + + if (!CanReadFromPacket(cs, packet, sizeof(n))) return 0; + + n = (uint32)packet->buffer[packet->pos++]; + n += (uint32)packet->buffer[packet->pos++] << 8; + n += (uint32)packet->buffer[packet->pos++] << 16; + n += (uint32)packet->buffer[packet->pos++] << 24; + return n; +} + +uint64 NetworkRecv_uint64(NetworkClientState *cs, Packet *packet) +{ + uint64 n; + + if (!CanReadFromPacket(cs, packet, sizeof(n))) return 0; + + n = (uint64)packet->buffer[packet->pos++]; + n += (uint64)packet->buffer[packet->pos++] << 8; + n += (uint64)packet->buffer[packet->pos++] << 16; + n += (uint64)packet->buffer[packet->pos++] << 24; + n += (uint64)packet->buffer[packet->pos++] << 32; + n += (uint64)packet->buffer[packet->pos++] << 40; + n += (uint64)packet->buffer[packet->pos++] << 48; + n += (uint64)packet->buffer[packet->pos++] << 56; + return n; +} + +/** Reads a string till it finds a '\0' in the stream */ +void NetworkRecv_string(NetworkClientState *cs, Packet *p, char *buffer, size_t size) +{ + PacketSize pos; + char *bufp = buffer; + + /* Don't allow reading from a closed socket */ + if (HasClientQuit(cs)) return; + + pos = p->pos; + while (--size > 0 && pos < p->size && (*buffer++ = p->buffer[pos++]) != '\0') {} + + if (size == 0 || pos == p->size) { + *buffer = '\0'; + /* If size was sooner to zero then the string in the stream + * skip till the \0, so than packet can be read out correctly for the rest */ + while (pos < p->size && p->buffer[pos] != '\0') pos++; + pos++; + } + p->pos = pos; + + str_validate(bufp); +} + +#endif /* ENABLE_NETWORK */ diff --git a/network/core/packet.h b/network/core/packet.h new file mode 100644 index 000000000..2510c69a7 --- /dev/null +++ b/network/core/packet.h @@ -0,0 +1,67 @@ +/* $Id$ */ + +#ifndef NETWORK_CORE_PACKET_H +#define NETWORK_CORE_PACKET_H + +#ifdef ENABLE_NETWORK + +/** + * @file packet.h Basic functions to create, fill and read packets. + */ + +typedef struct NetworkClientState NetworkClientState; + +/** + * Queries the network client state struct to determine whether + * the client has quit. It indirectly also queries whether the + * packet is corrupt as the connection will be closed if it is + * reading beyond the boundary of the received packet. + * @param cs the state to query + * @param true if the connection should be considered dropped + */ +bool HasClientQuit(NetworkClientState *cs); + +typedef uint16 PacketSize; ///< Size of the whole packet. +typedef uint8 PacketType; ///< Identifier for the packet + +/** + * Internal entity of a packet. As everything is sent as a packet, + * all network communication will need to call the functions that + * populate the packet. + * Every packet can be at most SEND_MTU bytes. Overflowing this + * limit will give an assertion when sending (i.e. writing) the + * packet. Reading past the size of the packet when receiving + * will return all 0 values and "" in case of the string. + */ +typedef struct Packet { + /** The next packet. Used for queueing packets before sending. */ + struct Packet *next; + /** The size of the whole packet for received packets. For packets + * that will be sent, the value is filled in just before the + * actual transmission. */ + PacketSize size; + /** The current read/write position in the packet */ + PacketSize pos; + /** The buffer of this packet */ + byte buffer[SEND_MTU]; +} Packet; + + +Packet *NetworkSend_Init(PacketType type); +void NetworkSend_FillPacketSize(Packet *packet); +void NetworkSend_uint8 (Packet *packet, uint8 data); +void NetworkSend_uint16(Packet *packet, uint16 data); +void NetworkSend_uint32(Packet *packet, uint32 data); +void NetworkSend_uint64(Packet *packet, uint64 data); +void NetworkSend_string(Packet *packet, const char* data); + +void NetworkRecv_ReadPacketSize(Packet *packet); +uint8 NetworkRecv_uint8 (NetworkClientState *cs, Packet *packet); +uint16 NetworkRecv_uint16(NetworkClientState *cs, Packet *packet); +uint32 NetworkRecv_uint32(NetworkClientState *cs, Packet *packet); +uint64 NetworkRecv_uint64(NetworkClientState *cs, Packet *packet); +void NetworkRecv_string(NetworkClientState *cs, Packet *packet, char* buffer, size_t size); + +#endif /* ENABLE_NETWORK */ + +#endif /* NETWORK_CORE_PACKET_H */ diff --git a/network/core/tcp.c b/network/core/tcp.c new file mode 100644 index 000000000..493a97dff --- /dev/null +++ b/network/core/tcp.c @@ -0,0 +1,227 @@ +/* $Id$ */ + +#ifdef ENABLE_NETWORK + +#include "../../stdafx.h" +#include "../../debug.h" +#include "../../openttd.h" +#include "../../variables.h" +#include "../../table/strings.h" +#include "../../functions.h" + +#include "os_abstraction.h" +#include "config.h" +#include "packet.h" +#include "../network_data.h" +#include "tcp.h" + +/** + * @file tcp.c Basic functions to receive and send TCP packets. + */ + +/** + * Functions to help NetworkRecv_Packet/NetworkSend_Packet a bit + * A socket can make errors. When that happens this handles what to do. + * For clients: close connection and drop back to main-menu + * For servers: close connection and that is it + * @param cs the client to close the connection of + * @return the new status + */ +NetworkRecvStatus CloseConnection(NetworkClientState *cs) +{ + NetworkCloseClient(cs); + + /* Clients drop back to the main menu */ + if (!_network_server && _networking) { + _switch_mode = SM_MENU; + _networking = false; + _switch_mode_errorstr = STR_NETWORK_ERR_LOSTCONNECTION; + + return NETWORK_RECV_STATUS_CONN_LOST; + } + + return NETWORK_RECV_STATUS_OKAY; +} + +/** + * Whether the client has quit or not (used in packet.c) + * @param cs the client to check + * @return true if the client has quit + */ +bool HasClientQuit(NetworkClientState *cs) +{ + return cs->has_quit; +} + +/** + * This function puts the packet in the send-queue and it is send as + * soon as possible. This is the next tick, or maybe one tick later + * if the OS-network-buffer is full) + * @param packet the packet to send + * @param cs the client to send to + */ +void NetworkSend_Packet(Packet *packet, NetworkClientState *cs) +{ + Packet *p; + assert(packet != NULL); + + packet->pos = 0; + packet->next = NULL; + + NetworkSend_FillPacketSize(packet); + + /* Locate last packet buffered for the client */ + p = cs->packet_queue; + if (p == NULL) { + /* No packets yet */ + cs->packet_queue = packet; + } else { + /* Skip to the last packet */ + while (p->next != NULL) p = p->next; + p->next = packet; + } +} + +/** + * Sends all the buffered packets out for this client. It stops when: + * 1) all packets are send (queue is empty) + * 2) the OS reports back that it can not send any more + * data right now (full network-buffer, it happens ;)) + * 3) sending took too long + * @param cs the client to send the packets for + */ +bool NetworkSend_Packets(NetworkClientState *cs) +{ + ssize_t res; + Packet *p; + + /* We can not write to this socket!! */ + if (!cs->writable) return false; + if (cs->socket == INVALID_SOCKET) return false; + + p = cs->packet_queue; + while (p != NULL) { + res = send(cs->socket, p->buffer + p->pos, p->size - p->pos, 0); + if (res == -1) { + int err = GET_LAST_ERROR(); + if (err != EWOULDBLOCK) { + /* Something went wrong.. close client! */ + DEBUG(net, 0, "send failed with error %d", err); + CloseConnection(cs); + return false; + } + return true; + } + if (res == 0) { + /* Client/server has left us :( */ + CloseConnection(cs); + return false; + } + + p->pos += res; + + /* Is this packet sent? */ + if (p->pos == p->size) { + /* Go to the next packet */ + cs->packet_queue = p->next; + free(p); + p = cs->packet_queue; + } else { + return true; + } + } + + return true; +} + +/** + * Receives a packet for the given client + * @param cs the client to (try to) receive a packet for + * @param status the variable to store the status into + * @return the received packet (or NULL when it didn't receive one) + */ +Packet *NetworkRecv_Packet(NetworkClientState *cs, NetworkRecvStatus *status) +{ + ssize_t res; + Packet *p; + + *status = NETWORK_RECV_STATUS_OKAY; + + if (cs->socket == INVALID_SOCKET) return NULL; + + if (cs->packet_recv == NULL) { + cs->packet_recv = malloc(sizeof(Packet)); + if (cs->packet_recv == NULL) error("Failed to allocate packet"); + /* Set pos to zero! */ + cs->packet_recv->pos = 0; + cs->packet_recv->size = 0; // Can be ommited, just for safety reasons + } + + p = cs->packet_recv; + + /* Read packet size */ + if (p->pos < sizeof(PacketSize)) { + while (p->pos < sizeof(PacketSize)) { + /* Read the size of the packet */ + res = recv(cs->socket, p->buffer + p->pos, sizeof(PacketSize) - p->pos, 0); + if (res == -1) { + int err = GET_LAST_ERROR(); + if (err != EWOULDBLOCK) { + /* Something went wrong... (104 is connection reset by peer) */ + if (err != 104) DEBUG(net, 0, "recv failed with error %d", err); + *status = CloseConnection(cs); + return NULL; + } + /* Connection would block, so stop for now */ + return NULL; + } + if (res == 0) { + /* Client/server has left */ + *status = CloseConnection(cs); + return NULL; + } + p->pos += res; + } + + NetworkRecv_ReadPacketSize(p); + + if (p->size > SEND_MTU) { + *status = CloseConnection(cs); + return NULL; + } + } + + /* Read rest of packet */ + while (p->pos < p->size) { + res = recv(cs->socket, p->buffer + p->pos, p->size - p->pos, 0); + if (res == -1) { + int err = GET_LAST_ERROR(); + if (err != EWOULDBLOCK) { + /* Something went wrong... (104 is connection reset by peer) */ + if (err != 104) DEBUG(net, 0, "recv failed with error %d", err); + *status = CloseConnection(cs); + return NULL; + } + /* Connection would block */ + return NULL; + } + if (res == 0) { + /* Client/server has left */ + *status = CloseConnection(cs); + return NULL; + } + + p->pos += res; + } + + /* We have a complete packet, return it! */ + p->pos = 2; + p->next = NULL; // Should not be needed, but who knows... + + /* Prepare for receiving a new packet */ + cs->packet_recv = NULL; + + return p; +} + +#endif /* ENABLE_NETWORK */ diff --git a/network/core/tcp.h b/network/core/tcp.h new file mode 100644 index 000000000..e3c307353 --- /dev/null +++ b/network/core/tcp.h @@ -0,0 +1,60 @@ +/* $Id$ */ + +#ifndef NETWORK_CORE_TCP_H +#define NETWORK_CORE_TCP_H + +#ifdef ENABLE_NETWORK + +/** + * @file tcp.h Basic functions to receive and send TCP packets. + */ + +/** + * Enum with all types of UDP packets. + * The order of the first 4 packets MUST not be changed, as + * it protects old clients from joining newer servers + * (because SERVER_ERROR is the respond to a wrong revision) + */ +enum { + PACKET_SERVER_FULL, + PACKET_SERVER_BANNED, + PACKET_CLIENT_JOIN, + PACKET_SERVER_ERROR, + PACKET_CLIENT_COMPANY_INFO, + PACKET_SERVER_COMPANY_INFO, + PACKET_SERVER_CLIENT_INFO, + PACKET_SERVER_NEED_PASSWORD, + PACKET_CLIENT_PASSWORD, + PACKET_SERVER_WELCOME, + PACKET_CLIENT_GETMAP, + PACKET_SERVER_WAIT, + PACKET_SERVER_MAP, + PACKET_CLIENT_MAP_OK, + PACKET_SERVER_JOIN, + PACKET_SERVER_FRAME, + PACKET_SERVER_SYNC, + PACKET_CLIENT_ACK, + PACKET_CLIENT_COMMAND, + PACKET_SERVER_COMMAND, + PACKET_CLIENT_CHAT, + PACKET_SERVER_CHAT, + PACKET_CLIENT_SET_PASSWORD, + PACKET_CLIENT_SET_NAME, + PACKET_CLIENT_QUIT, + PACKET_CLIENT_ERROR, + PACKET_SERVER_QUIT, + PACKET_SERVER_ERROR_QUIT, + PACKET_SERVER_SHUTDOWN, + PACKET_SERVER_NEWGAME, + PACKET_SERVER_RCON, + PACKET_CLIENT_RCON, + PACKET_END ///< Must ALWAYS be on the end of this list!! (period) +}; + +void NetworkSend_Packet(Packet *packet, NetworkClientState *cs); +Packet *NetworkRecv_Packet(NetworkClientState *cs, NetworkRecvStatus *status); +bool NetworkSend_Packets(NetworkClientState *cs); + +#endif /* ENABLE_NETWORK */ + +#endif /* NETWORK_CORE_TCP_H */ diff --git a/network/core/udp.c b/network/core/udp.c new file mode 100644 index 000000000..badd9a532 --- /dev/null +++ b/network/core/udp.c @@ -0,0 +1,277 @@ +/* $Id$ */ + +#ifdef ENABLE_NETWORK + +#include "../../stdafx.h" +#include "../../date.h" +#include "../../debug.h" +#include "../../macros.h" +#include "../../newgrf_config.h" + +#include "os_abstraction.h" +#include "config.h" +#include "game.h" +#include "packet.h" +#include "udp.h" + +/** + * @file udp.c Basic functions to receive and send UDP packets. + */ + +/** + * Send a packet over UDP + * @param udp the socket to send over + * @param p the packet to send + * @param recv the receiver (target) of the packet + */ +void NetworkSendUDP_Packet(SOCKET udp, Packet *p, struct sockaddr_in *recv) +{ + int res; + + NetworkSend_FillPacketSize(p); + + /* Send the buffer */ + res = sendto(udp, p->buffer, p->size, 0, (struct sockaddr *)recv, sizeof(*recv)); + + /* Check for any errors, but ignore it otherwise */ + if (res == -1) DEBUG(net, 1, "[udp] sendto failed with: %i", GET_LAST_ERROR()); +} + +/** + * Start listening on the given host and port. + * @param udp the place where the (references to the) UDP are stored + * @param host the host (ip) to listen on + * @param port the port to listen on + * @param broadcast whether to allow broadcast sending/receiving + * @return true if the listening succeeded + */ +bool NetworkUDPListen(SOCKET *udp, uint32 host, uint16 port, bool broadcast) +{ + struct sockaddr_in sin; + + /* Make sure socket is closed */ + closesocket(*udp); + + *udp = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (*udp == INVALID_SOCKET) { + DEBUG(net, 0, "[udp] failed to start UDP listener"); + return false; + } + + /* set nonblocking mode for socket */ + { + unsigned long blocking = 1; +#ifndef BEOS_NET_SERVER + ioctlsocket(*udp, FIONBIO, &blocking); +#else + setsockopt(*udp, SOL_SOCKET, SO_NONBLOCK, &blocking, NULL); +#endif + } + + sin.sin_family = AF_INET; + /* Listen on all IPs */ + sin.sin_addr.s_addr = host; + sin.sin_port = htons(port); + + if (bind(*udp, (struct sockaddr*)&sin, sizeof(sin)) != 0) { + DEBUG(net, 0, "[udp] bind failed on %s:%i", inet_ntoa(*(struct in_addr *)&host), port); + return false; + } + + if (broadcast) { + /* Enable broadcast */ + unsigned long val = 1; +#ifndef BEOS_NET_SERVER // will work around this, some day; maybe. + setsockopt(*udp, SOL_SOCKET, SO_BROADCAST, (char *) &val , sizeof(val)); +#endif + } + + DEBUG(net, 1, "[udp] listening on port %s:%d", inet_ntoa(*(struct in_addr *)&host), port); + + return true; +} + +/** + * Receive a packet at UDP level + * @param udp the socket to receive the packet on + */ +void NetworkUDPReceive(SOCKET udp) +{ + struct sockaddr_in client_addr; + socklen_t client_len; + int nbytes; + Packet p; + int packet_len; + + packet_len = sizeof(p.buffer); + client_len = sizeof(client_addr); + + /* Try to receive anything */ + nbytes = recvfrom(udp, p.buffer, packet_len, 0, (struct sockaddr *)&client_addr, &client_len); + + /* We got some bytes for the base header of the packet. + * Assume we received the whole packet. */ + if (nbytes > 2) { + NetworkRecv_ReadPacketSize(&p); + + /* Put the position on the right place */ + p.pos = 2; + p.next = NULL; + + /* Handle the packet */ + NetworkHandleUDPPacket(&p, &client_addr); + } +} + + +/** + * Serializes the GRFIdentifier (GRF ID and MD5 checksum) to the packet + * @param p the packet to write the data to + * @param c the configuration to write the GRF ID and MD5 checksum from + */ +void NetworkSend_GRFIdentifier(Packet *p, const GRFConfig *c) +{ + uint j; + NetworkSend_uint32(p, c->grfid); + for (j = 0; j < sizeof(c->md5sum); j++) { + NetworkSend_uint8 (p, c->md5sum[j]); + } +} + +/** + * Deserializes the GRFIdentifier (GRF ID and MD5 checksum) from the packet + * @param cs the client state (for closing connect on out-of-bounds reading etc) + * @param p the packet to read the data from + * @param c the configuration to write the GRF ID and MD5 checksum to + */ +void NetworkRecv_GRFIdentifier(NetworkClientState *cs, Packet *p, GRFConfig *c) +{ + uint j; + c->grfid = NetworkRecv_uint32(cs, p); + for (j = 0; j < sizeof(c->md5sum); j++) { + c->md5sum[j] = NetworkRecv_uint8(cs, p); + } +} + + +/** + * Serializes the NetworkGameInfo struct to the packet + * @param p the packet to write the data to + * @param info the NetworkGameInfo struct to serialize + */ +void NetworkSend_NetworkGameInfo(Packet *p, const NetworkGameInfo *info) +{ + NetworkSend_uint8 (p, NETWORK_GAME_INFO_VERSION); + + /* + * Please observe the order. + * The parts must be read in the same order as they are sent! + */ + + + /* NETWORK_GAME_INFO_VERSION = 4 */ + { + /* Only send the GRF Identification (GRF_ID and MD5 checksum) of + * the GRFs that are needed, i.e. the ones that the server has + * selected in the NewGRF GUI and not the ones that are used due + * to the fact that they are in [newgrf-static] in openttd.cfg */ + const GRFConfig *c; + uint count = 0; + + /* Count number of GRFs to send information about */ + for (c = info->grfconfig; c != NULL; c = c->next) { + if (!HASBIT(c->flags, GCF_STATIC)) count++; + } + NetworkSend_uint8 (p, count); // Send number of GRFs + + /* Send actual GRF Identifications */ + for (c = info->grfconfig; c != NULL; c = c->next) { + if (!HASBIT(c->flags, GCF_STATIC)) NetworkSend_GRFIdentifier(p, c); + } + } + + /* NETWORK_GAME_INFO_VERSION = 3 */ + NetworkSend_uint32(p, info->game_date); + NetworkSend_uint32(p, info->start_date); + + /* NETWORK_GAME_INFO_VERSION = 2 */ + NetworkSend_uint8 (p, info->companies_max); + NetworkSend_uint8 (p, info->companies_on); + NetworkSend_uint8 (p, info->spectators_max); + + /* NETWORK_GAME_INFO_VERSION = 1 */ + NetworkSend_string(p, info->server_name); + NetworkSend_string(p, info->server_revision); + NetworkSend_uint8 (p, info->server_lang); + NetworkSend_uint8 (p, info->use_password); + NetworkSend_uint8 (p, info->clients_max); + NetworkSend_uint8 (p, info->clients_on); + NetworkSend_uint8 (p, info->spectators_on); + NetworkSend_string(p, info->map_name); + NetworkSend_uint16(p, info->map_width); + NetworkSend_uint16(p, info->map_height); + NetworkSend_uint8 (p, info->map_set); + NetworkSend_uint8 (p, info->dedicated); +} + +/** + * Deserializes the NetworkGameInfo struct from the packet + * @param cs the client state (for closing connect on out-of-bounds reading etc) + * @param p the packet to read the data from + * @param info the NetworkGameInfo to deserialize into + */ +void NetworkRecv_NetworkGameInfo(NetworkClientState *cs, Packet *p, NetworkGameInfo *info) +{ + info->game_info_version = NetworkRecv_uint8(cs, p); + + /* + * Please observe the order. + * The parts must be read in the same order as they are sent! + */ + + switch (info->game_info_version) { + case 4: { + GRFConfig *c, **dst = &info->grfconfig; + uint i; + uint num_grfs = NetworkRecv_uint8(cs, p); + + for (i = 0; i < num_grfs; i++) { + c = calloc(1, sizeof(*c)); + NetworkRecv_GRFIdentifier(cs, p, c); + HandleIncomingNetworkGameInfoGRFConfig(c); + + /* Append GRFConfig to the list */ + *dst = c; + dst = &c->next; + } + } /* Fallthrough */ + case 3: + info->game_date = NetworkRecv_uint32(cs, p); + info->start_date = NetworkRecv_uint32(cs, p); + /* Fallthrough */ + case 2: + info->companies_max = NetworkRecv_uint8 (cs, p); + info->companies_on = NetworkRecv_uint8 (cs, p); + info->spectators_max = NetworkRecv_uint8 (cs, p); + /* Fallthrough */ + case 1: + NetworkRecv_string(cs, p, info->server_name, sizeof(info->server_name)); + NetworkRecv_string(cs, p, info->server_revision, sizeof(info->server_revision)); + info->server_lang = NetworkRecv_uint8 (cs, p); + info->use_password = NetworkRecv_uint8 (cs, p); + info->clients_max = NetworkRecv_uint8 (cs, p); + info->clients_on = NetworkRecv_uint8 (cs, p); + info->spectators_on = NetworkRecv_uint8 (cs, p); + if (info->game_info_version < 3) { // 16 bits dates got scrapped and are read earlier + info->game_date = NetworkRecv_uint16(cs, p) + DAYS_TILL_ORIGINAL_BASE_YEAR; + info->start_date = NetworkRecv_uint16(cs, p) + DAYS_TILL_ORIGINAL_BASE_YEAR; + } + NetworkRecv_string(cs, p, info->map_name, sizeof(info->map_name)); + info->map_width = NetworkRecv_uint16(cs, p); + info->map_height = NetworkRecv_uint16(cs, p); + info->map_set = NetworkRecv_uint8 (cs, p); + info->dedicated = NetworkRecv_uint8 (cs, p); + } +} + +#endif /* ENABLE_NETWORK */ diff --git a/network/core/udp.h b/network/core/udp.h new file mode 100644 index 000000000..3221e664f --- /dev/null +++ b/network/core/udp.h @@ -0,0 +1,62 @@ +/* $Id$ */ + +#ifndef NETWORK_CORE_UDP_H +#define NETWORK_CORE_UDP_H + +#ifdef ENABLE_NETWORK + +/** + * @file udp.h Basic functions to receive and send UDP packets. + */ + +///** Sending/receiving of UDP packets **//// + +void NetworkSendUDP_Packet(SOCKET udp, Packet *p, struct sockaddr_in *recv); +bool NetworkUDPListen(SOCKET *udp, uint32 host, uint16 port, bool broadcast); +void NetworkUDPReceive(SOCKET udp); + +/** + * Function that is called for every received UDP packet. + * @param packet the received packet + * @param client_addr the address of the sender of the packet + */ +void NetworkHandleUDPPacket(Packet *p, struct sockaddr_in *client_addr); + + +///** Sending/receiving of (large) chuncks of UDP packets **//// + + +/** Enum with all types of UDP packets. The order MUST not be changed **/ +enum { + PACKET_UDP_CLIENT_FIND_SERVER, ///< Queries a game server for game information + PACKET_UDP_SERVER_RESPONSE, ///< Reply of the game server with game information + PACKET_UDP_CLIENT_DETAIL_INFO, ///< Queries a game server about details of the game, such as companies + PACKET_UDP_SERVER_DETAIL_INFO, ///< Reply of the game server about details of the game, such as companies + PACKET_UDP_SERVER_REGISTER, ///< Packet to register itself to the master server + PACKET_UDP_MASTER_ACK_REGISTER, ///< Packet indicating registration has succedeed + PACKET_UDP_CLIENT_GET_LIST, ///< Request for serverlist from master server + PACKET_UDP_MASTER_RESPONSE_LIST, ///< Response from master server with server ip's + port's + PACKET_UDP_SERVER_UNREGISTER, ///< Request to be removed from the server-list + PACKET_UDP_CLIENT_GET_NEWGRFS, ///< Requests the name for a list of GRFs (GRF_ID and MD5) + PACKET_UDP_SERVER_NEWGRFS, ///< Sends the list of NewGRF's requested. + PACKET_UDP_END ///< Must ALWAYS be on the end of this list!! (period) +}; + +void NetworkSend_GRFIdentifier(Packet *p, const GRFConfig *c); +void NetworkSend_NetworkGameInfo(Packet *p, const NetworkGameInfo *info); + +void NetworkRecv_GRFIdentifier(NetworkClientState *cs, Packet *p, GRFConfig *c); +void NetworkRecv_NetworkGameInfo(NetworkClientState *cs, Packet *p, NetworkGameInfo *info); + +/** + * Function that is called for every GRFConfig that is read when receiving + * a NetworkGameInfo. Only grfid and md5sum are set, the rest is zero. This + * function must set all appropriate fields. This GRF is later appended to + * the grfconfig list of the NetworkGameInfo. + * @param config the GRF to handle + */ +void HandleIncomingNetworkGameInfoGRFConfig(GRFConfig *config); + +#endif /* ENABLE_NETWORK */ + +#endif /* NETWORK_CORE_UDP_H */ -- cgit v1.2.3-70-g09d2