diff -r d4d00a16ef26 -r bfa6074e2833 network.c --- a/network.c Tue Jan 02 18:40:37 2007 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1448 +0,0 @@ -/* $Id$ */ - -#include "stdafx.h" -#include "network_data.h" - -#if defined(WITH_REV) - extern const char _openttd_revision[]; -#elif defined(WITH_REV_HACK) - #define WITH_REV - const char _openttd_revision[] = WITH_REV_HACK; -#else - const char _openttd_revision[] = NOREV_STRING; -#endif - - -#ifdef ENABLE_NETWORK - -#include "openttd.h" -#include "debug.h" -#include "functions.h" -#include "string.h" -#include "strings.h" -#include "map.h" -#include "command.h" -#include "variables.h" -#include "date.h" -#include "table/strings.h" -#include "network_client.h" -#include "network_server.h" -#include "network_udp.h" -#include "network_gamelist.h" -#include "network_gui.h" -#include "console.h" /* IConsoleCmdExec */ -#include /* va_list */ -#include "md5.h" - -#ifdef __MORPHOS__ -// the library base is required here -struct Library *SocketBase = NULL; -#endif - -// The listen socket for the server -static SOCKET _listensocket; - -// The amount of clients connected -static byte _network_clients_connected = 0; -// The index counter for new clients (is never decreased) -static uint16 _network_client_index = NETWORK_SERVER_INDEX + 1; - -/* Some externs / forwards */ -extern void StateGameLoop(void); - -// Function that looks up the CI for a given client-index -NetworkClientInfo *NetworkFindClientInfoFromIndex(uint16 client_index) -{ - NetworkClientInfo *ci; - - for (ci = _network_client_info; ci != endof(_network_client_info); ci++) { - if (ci->client_index == client_index) return ci; - } - - return NULL; -} - -/** Return the CI for a given IP - * @param ip IP of the client we are looking for. This must be in string-format - * @return return a pointer to the corresponding NetworkClientInfo struct or NULL on failure */ -NetworkClientInfo *NetworkFindClientInfoFromIP(const char *ip) -{ - NetworkClientInfo *ci; - uint32 ip_number = inet_addr(ip); - - for (ci = _network_client_info; ci != endof(_network_client_info); ci++) { - if (ci->client_ip == ip_number) return ci; - } - - return NULL; -} - -// Function that looks up the CS for a given client-index -NetworkClientState *NetworkFindClientStateFromIndex(uint16 client_index) -{ - NetworkClientState *cs; - - for (cs = _clients; cs != &_clients[MAX_CLIENT_INFO]; cs++) { - if (cs->index == client_index) return cs; - } - - return NULL; -} - -// NetworkGetClientName is a server-safe function to get the name of the client -// if the user did not send it yet, Client # is used. -void NetworkGetClientName(char *client_name, size_t size, const NetworkClientState *cs) -{ - const NetworkClientInfo *ci = DEREF_CLIENT_INFO(cs); - - if (ci->client_name[0] == '\0') { - snprintf(client_name, size, "Client #%4d", cs->index); - } else { - ttd_strlcpy(client_name, ci->client_name, size); - } -} - -byte NetworkSpectatorCount(void) -{ - const NetworkClientState *cs; - byte count = 0; - - FOR_ALL_CLIENTS(cs) { - if (DEREF_CLIENT_INFO(cs)->client_playas == PLAYER_SPECTATOR) count++; - } - - return count; -} - -// This puts a text-message to the console, or in the future, the chat-box, -// (to keep it all a bit more general) -// If 'self_send' is true, this is the client who is sending the message -void CDECL NetworkTextMessage(NetworkAction action, uint16 color, bool self_send, const char *name, const char *str, ...) -{ - char buf[1024]; - va_list va; - const int duration = 10; // Game days the messages stay visible - char message[1024]; - char temp[1024]; - - va_start(va, str); - vsnprintf(buf, lengthof(buf), str, va); - va_end(va); - - switch (action) { - case NETWORK_ACTION_SERVER_MESSAGE: - color = 1; - snprintf(message, sizeof(message), "*** %s", buf); - break; - case NETWORK_ACTION_JOIN: - color = 1; - GetString(temp, STR_NETWORK_CLIENT_JOINED, lastof(temp)); - snprintf(message, sizeof(message), "*** %s %s", name, temp); - break; - case NETWORK_ACTION_LEAVE: - color = 1; - GetString(temp, STR_NETWORK_ERR_LEFT, lastof(temp)); - snprintf(message, sizeof(message), "*** %s %s (%s)", name, temp, buf); - break; - case NETWORK_ACTION_GIVE_MONEY: - if (self_send) { - SetDParamStr(0, name); - SetDParam(1, atoi(buf)); - GetString(temp, STR_NETWORK_GAVE_MONEY_AWAY, lastof(temp)); - snprintf(message, sizeof(message), "*** %s", temp); - } else { - SetDParam(0, atoi(buf)); - GetString(temp, STR_NETWORK_GIVE_MONEY, lastof(temp)); - snprintf(message, sizeof(message), "*** %s %s", name, temp); - } - break; - case NETWORK_ACTION_NAME_CHANGE: - GetString(temp, STR_NETWORK_NAME_CHANGE, lastof(temp)); - snprintf(message, sizeof(message), "*** %s %s %s", name, temp, buf); - break; - case NETWORK_ACTION_CHAT_COMPANY: - SetDParamStr(0, name); - SetDParamStr(1, buf); - GetString(temp, self_send ? STR_NETWORK_CHAT_TO_COMPANY : STR_NETWORK_CHAT_COMPANY, lastof(temp)); - ttd_strlcpy(message, temp, sizeof(message)); - break; - case NETWORK_ACTION_CHAT_CLIENT: - SetDParamStr(0, name); - SetDParamStr(1, buf); - GetString(temp, self_send ? STR_NETWORK_CHAT_TO_CLIENT : STR_NETWORK_CHAT_CLIENT, lastof(temp)); - ttd_strlcpy(message, temp, sizeof(message)); - break; - default: - SetDParamStr(0, name); - SetDParamStr(1, buf); - GetString(temp, STR_NETWORK_CHAT_ALL, lastof(temp)); - ttd_strlcpy(message, temp, sizeof(message)); - break; - } - - IConsolePrintF(color, "%s", message); - AddTextMessage(color, duration, "%s", message); -} - -// Calculate the frame-lag of a client -uint NetworkCalculateLag(const NetworkClientState *cs) -{ - int lag = cs->last_frame_server - cs->last_frame; - // This client has missed his ACK packet after 1 DAY_TICKS.. - // so we increase his lag for every frame that passes! - // The packet can be out by a max of _net_frame_freq - if (cs->last_frame_server + DAY_TICKS + _network_frame_freq < _frame_counter) - lag += _frame_counter - (cs->last_frame_server + DAY_TICKS + _network_frame_freq); - - return lag; -} - - -// There was a non-recoverable error, drop back to the main menu with a nice -// error -static void NetworkError(StringID error_string) -{ - _switch_mode = SM_MENU; - _switch_mode_errorstr = error_string; -} - -static void ClientStartError(const char *error) -{ - DEBUG(net, 0, "[client] could not start network: %s",error); - NetworkError(STR_NETWORK_ERR_CLIENT_START); -} - -static void ServerStartError(const char *error) -{ - DEBUG(net, 0, "[server] could not start network: %s",error); - NetworkError(STR_NETWORK_ERR_SERVER_START); -} - -static void NetworkClientError(NetworkRecvStatus res, NetworkClientState* cs) -{ - // First, send a CLIENT_ERROR to the server, so he knows we are - // disconnection (and why!) - NetworkErrorCode errorno; - - // We just want to close the connection.. - if (res == NETWORK_RECV_STATUS_CLOSE_QUERY) { - cs->has_quit = true; - NetworkCloseClient(cs); - _networking = false; - - DeleteWindowById(WC_NETWORK_STATUS_WINDOW, 0); - return; - } - - switch (res) { - case NETWORK_RECV_STATUS_DESYNC: errorno = NETWORK_ERROR_DESYNC; break; - case NETWORK_RECV_STATUS_SAVEGAME: errorno = NETWORK_ERROR_SAVEGAME_FAILED; break; - default: errorno = NETWORK_ERROR_GENERAL; break; - } - // This means we fucked up and the server closed the connection - if (res != NETWORK_RECV_STATUS_SERVER_ERROR && res != NETWORK_RECV_STATUS_SERVER_FULL && - res != NETWORK_RECV_STATUS_SERVER_BANNED) { - SEND_COMMAND(PACKET_CLIENT_ERROR)(errorno); - - // Dequeue all commands before closing the socket - NetworkSend_Packets(DEREF_CLIENT(0)); - } - - _switch_mode = SM_MENU; - NetworkCloseClient(cs); - _networking = false; -} - -/** Retrieve a string representation of an internal error number - * @param buf buffer where the error message will be stored - * @param err NetworkErrorCode - * @return returns a pointer to the error message (buf) */ -char* GetNetworkErrorMsg(char* buf, NetworkErrorCode err, const char* last) -{ - /* List of possible network errors, used by - * PACKET_SERVER_ERROR and PACKET_CLIENT_ERROR */ - static const StringID network_error_strings[] = { - STR_NETWORK_ERR_CLIENT_GENERAL, - STR_NETWORK_ERR_CLIENT_DESYNC, - STR_NETWORK_ERR_CLIENT_SAVEGAME, - STR_NETWORK_ERR_CLIENT_CONNECTION_LOST, - STR_NETWORK_ERR_CLIENT_PROTOCOL_ERROR, - STR_NETWORK_ERR_CLIENT_NOT_AUTHORIZED, - STR_NETWORK_ERR_CLIENT_NOT_EXPECTED, - STR_NETWORK_ERR_CLIENT_WRONG_REVISION, - STR_NETWORK_ERR_CLIENT_NAME_IN_USE, - STR_NETWORK_ERR_CLIENT_WRONG_PASSWORD, - STR_NETWORK_ERR_CLIENT_PLAYER_MISMATCH, - STR_NETWORK_ERR_CLIENT_KICKED, - STR_NETWORK_ERR_CLIENT_CHEATER, - STR_NETWORK_ERR_CLIENT_SERVER_FULL - }; - - if (err >= lengthof(network_error_strings)) err = 0; - - return GetString(buf, network_error_strings[err], last); -} - -/* Count the number of active clients connected */ -static uint NetworkCountPlayers(void) -{ - const NetworkClientState *cs; - uint count = 0; - - FOR_ALL_CLIENTS(cs) { - const NetworkClientInfo *ci = DEREF_CLIENT_INFO(cs); - if (IsValidPlayer(ci->client_playas)) count++; - } - - return count; -} - -static bool _min_players_paused = false; - -/* Check if the minimum number of players has been reached and pause or unpause the game as appropriate */ -void CheckMinPlayers(void) -{ - if (!_network_dedicated) return; - - if (NetworkCountPlayers() < _network_min_players) { - if (_min_players_paused) return; - - _min_players_paused = true; - DoCommandP(0, 1, 0, NULL, CMD_PAUSE); - NetworkServer_HandleChat(NETWORK_ACTION_SERVER_MESSAGE, DESTTYPE_BROADCAST, 0, "Game paused (not enough players)", NETWORK_SERVER_INDEX); - } else { - if (!_min_players_paused) return; - - _min_players_paused = false; - DoCommandP(0, 0, 0, NULL, CMD_PAUSE); - NetworkServer_HandleChat(NETWORK_ACTION_SERVER_MESSAGE, DESTTYPE_BROADCAST, 0, "Game unpaused (enough players)", NETWORK_SERVER_INDEX); - } -} - -// Find all IP-aliases for this host -static void NetworkFindIPs(void) -{ - int i; - -#if defined(BEOS_NET_SERVER) /* doesn't have neither getifaddrs or net/if.h */ - /* Based on Andrew Bachmann's netstat+.c. Big thanks to him! */ - int _netstat(int fd, char **output, int verbose); - - int seek_past_header(char **pos, const char *header) { - char *new_pos = strstr(*pos, header); - if (new_pos == 0) { - return B_ERROR; - } - *pos += strlen(header) + new_pos - *pos + 1; - return B_OK; - } - - int output_length; - char *output_pointer = NULL; - char **output; - int sock = socket(AF_INET, SOCK_DGRAM, 0); - i = 0; - - // If something fails, make sure the list is empty - _broadcast_list[0] = 0; - - if (sock < 0) { - DEBUG(net, 0, "[core] error creating socket"); - return; - } - - output_length = _netstat(sock, &output_pointer, 1); - if (output_length < 0) { - DEBUG(net, 0, "[core] error running _netstat"); - return; - } - - output = &output_pointer; - if (seek_past_header(output, "IP Interfaces:") == B_OK) { - for (;;) { - uint32 n, fields, read; - uint8 i1, i2, i3, i4, j1, j2, j3, j4; - struct in_addr inaddr; - uint32 ip; - uint32 netmask; - - fields = sscanf(*output, "%u: %hhu.%hhu.%hhu.%hhu, netmask %hhu.%hhu.%hhu.%hhu%n", - &n, &i1,&i2,&i3,&i4, &j1,&j2,&j3,&j4, &read); - read += 1; - if (fields != 9) { - break; - } - - ip = (uint32)i1 << 24 | (uint32)i2 << 16 | (uint32)i3 << 8 | (uint32)i4; - netmask = (uint32)j1 << 24 | (uint32)j2 << 16 | (uint32)j3 << 8 | (uint32)j4; - - if (ip != INADDR_LOOPBACK && ip != INADDR_ANY) { - inaddr.s_addr = htonl(ip | ~netmask); - _broadcast_list[i] = inaddr.s_addr; - i++; - } - if (read < 0) { - break; - } - *output += read; - } - /* XXX - Using either one of these crashes openttd heavily? - wber */ - /*free(output_pointer);*/ - /*free(output);*/ - closesocket(sock); - } -#elif defined(HAVE_GETIFADDRS) - struct ifaddrs *ifap, *ifa; - - // If something fails, make sure the list is empty - _broadcast_list[0] = 0; - - if (getifaddrs(&ifap) != 0) - return; - - i = 0; - for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) { - if (!(ifa->ifa_flags & IFF_BROADCAST)) continue; - if (ifa->ifa_broadaddr == NULL) continue; - if (ifa->ifa_broadaddr->sa_family != AF_INET) continue; - _broadcast_list[i] = ((struct sockaddr_in*)ifa->ifa_broadaddr)->sin_addr.s_addr; - i++; - } - freeifaddrs(ifap); - -#else /* not HAVE_GETIFADDRS */ - SOCKET sock; -#ifdef WIN32 - DWORD len = 0; - INTERFACE_INFO ifo[MAX_INTERFACES]; - uint j; -#else - char buf[4 * 1024]; // Arbitrary buffer size - struct ifconf ifconf; - const char* buf_end; - const char* p; -#endif - - // If something fails, make sure the list is empty - _broadcast_list[0] = 0; - - sock = socket(AF_INET, SOCK_DGRAM, 0); - if (sock == INVALID_SOCKET) return; - -#ifdef WIN32 - memset(&ifo[0], 0, sizeof(ifo)); - if ((WSAIoctl(sock, SIO_GET_INTERFACE_LIST, NULL, 0, &ifo[0], sizeof(ifo), &len, NULL, NULL)) != 0) { - closesocket(sock); - return; - } - - i = 0; - for (j = 0; j < len / sizeof(*ifo); j++) { - if (ifo[j].iiFlags & IFF_LOOPBACK) continue; - if (!(ifo[j].iiFlags & IFF_BROADCAST)) continue; - /* iiBroadcast is unusable, because it always seems to be set to - * 255.255.255.255. - */ - _broadcast_list[i++] = - ifo[j].iiAddress.AddressIn.sin_addr.s_addr | - ~ifo[j].iiNetmask.AddressIn.sin_addr.s_addr; - } -#else - ifconf.ifc_len = sizeof(buf); - ifconf.ifc_buf = buf; - if (ioctl(sock, SIOCGIFCONF, &ifconf) == -1) { - closesocket(sock); - return; - } - - i = 0; - buf_end = buf + ifconf.ifc_len; - for (p = buf; p < buf_end;) { - const struct ifreq* req = (const struct ifreq*)p; - - if (req->ifr_addr.sa_family == AF_INET) { - struct ifreq r; - - strncpy(r.ifr_name, req->ifr_name, lengthof(r.ifr_name)); - if (ioctl(sock, SIOCGIFFLAGS, &r) != -1 && - r.ifr_flags & IFF_BROADCAST && - ioctl(sock, SIOCGIFBRDADDR, &r) != -1) { - _broadcast_list[i++] = - ((struct sockaddr_in*)&r.ifr_broadaddr)->sin_addr.s_addr; - } - } - - p += sizeof(struct ifreq); -#ifdef AF_LINK - p += req->ifr_addr.sa_len - sizeof(struct sockaddr); -#endif - } -#endif - - closesocket(sock); -#endif /* not HAVE_GETIFADDRS */ - - _broadcast_list[i] = 0; - - DEBUG(net, 3, "Detected broadcast addresses:"); - // Now display to the debug all the detected ips - for (i = 0; _broadcast_list[i] != 0; i++) { - DEBUG(net, 3, "%d) %s", i, inet_ntoa(*(struct in_addr *)&_broadcast_list[i]));//inet_ntoa(inaddr)); - } -} - -// Resolve a hostname to a inet_addr -unsigned long NetworkResolveHost(const char *hostname) -{ - in_addr_t ip; - - // First try: is it an ip address? - ip = inet_addr(hostname); - - // If not try to resolve the name - if (ip == INADDR_NONE) { - struct hostent *he = gethostbyname(hostname); - if (he == NULL) { - DEBUG(net, 0, "Cannot resolve '%s'", hostname); - } else { - struct in_addr addr = *(struct in_addr *)he->h_addr_list[0]; - DEBUG(net, 1, "Resolved '%s' to %s", hostname, inet_ntoa(addr)); - ip = addr.s_addr; - } - } - return ip; -} - -// Converts a string to ip/port/player -// Format: IP#player:port -// -// connection_string will be re-terminated to seperate out the hostname, and player and port will -// be set to the player and port strings given by the user, inside the memory area originally -// occupied by connection_string. -void ParseConnectionString(const char **player, const char **port, char *connection_string) -{ - char *p; - for (p = connection_string; *p != '\0'; p++) { - if (*p == '#') { - *p = '\0'; - *player = ++p; - while (IsValidChar(*p, CS_NUMERAL)) p++; - if (*p == '\0') break; - } else if (*p == ':') { - *port = p + 1; - *p = '\0'; - } - } -} - -// Creates a new client from a socket -// Used both by the server and the client -static NetworkClientState *NetworkAllocClient(SOCKET s) -{ - NetworkClientState *cs; - byte client_no = 0; - - if (_network_server) { - // Can we handle a new client? - if (_network_clients_connected >= MAX_CLIENTS) return NULL; - if (_network_game_info.clients_on >= _network_game_info.clients_max) return NULL; - - // Register the login - client_no = _network_clients_connected++; - } - - cs = DEREF_CLIENT(client_no); - memset(cs, 0, sizeof(*cs)); - cs->socket = s; - cs->last_frame = 0; - cs->has_quit = false; - - cs->last_frame = _frame_counter; - cs->last_frame_server = _frame_counter; - - if (_network_server) { - NetworkClientInfo *ci = DEREF_CLIENT_INFO(cs); - memset(ci, 0, sizeof(*ci)); - - cs->index = _network_client_index++; - ci->client_index = cs->index; - ci->client_playas = PLAYER_INACTIVE_CLIENT; - ci->join_date = _date; - - InvalidateWindow(WC_CLIENT_LIST, 0); - } - - return cs; -} - -// Close a connection -void NetworkCloseClient(NetworkClientState *cs) -{ - NetworkClientInfo *ci; - // Socket is already dead - if (cs->socket == INVALID_SOCKET) { - cs->has_quit = true; - return; - } - - DEBUG(net, 1, "Closed client connection %d", cs->index); - - if (!cs->has_quit && _network_server && cs->status > STATUS_INACTIVE) { - // We did not receive a leave message from this client... - NetworkErrorCode errorno = NETWORK_ERROR_CONNECTION_LOST; - char str[100]; - char client_name[NETWORK_CLIENT_NAME_LENGTH]; - NetworkClientState *new_cs; - - NetworkGetClientName(client_name, sizeof(client_name), cs); - - GetNetworkErrorMsg(str, errorno, lastof(str)); - - NetworkTextMessage(NETWORK_ACTION_LEAVE, 1, false, client_name, "%s", str); - - // Inform other clients of this... strange leaving ;) - FOR_ALL_CLIENTS(new_cs) { - if (new_cs->status > STATUS_AUTH && cs != new_cs) { - SEND_COMMAND(PACKET_SERVER_ERROR_QUIT)(new_cs, cs->index, errorno); - } - } - } - - /* When the client was PRE_ACTIVE, the server was in pause mode, so unpause */ - if (cs->status == STATUS_PRE_ACTIVE && _network_pause_on_join) { - DoCommandP(0, 0, 0, NULL, CMD_PAUSE); - NetworkServer_HandleChat(NETWORK_ACTION_SERVER_MESSAGE, DESTTYPE_BROADCAST, 0, "Game unpaused", NETWORK_SERVER_INDEX); - } - - closesocket(cs->socket); - cs->writable = false; - cs->has_quit = true; - - // Free all pending and partially received packets - while (cs->packet_queue != NULL) { - Packet *p = cs->packet_queue->next; - free(cs->packet_queue); - cs->packet_queue = p; - } - free(cs->packet_recv); - cs->packet_recv = NULL; - - while (cs->command_queue != NULL) { - CommandPacket *p = cs->command_queue->next; - free(cs->command_queue); - cs->command_queue = p; - } - - // Close the gap in the client-list - ci = DEREF_CLIENT_INFO(cs); - - if (_network_server) { - // We just lost one client :( - if (cs->status > STATUS_INACTIVE) _network_game_info.clients_on--; - _network_clients_connected--; - - while ((cs + 1) != DEREF_CLIENT(MAX_CLIENTS) && (cs + 1)->socket != INVALID_SOCKET) { - *cs = *(cs + 1); - *ci = *(ci + 1); - cs++; - ci++; - } - - InvalidateWindow(WC_CLIENT_LIST, 0); - } - - // Reset the status of the last socket - cs->socket = INVALID_SOCKET; - cs->status = STATUS_INACTIVE; - cs->index = NETWORK_EMPTY_INDEX; - ci->client_index = NETWORK_EMPTY_INDEX; - - CheckMinPlayers(); -} - -// A client wants to connect to a server -static bool NetworkConnect(const char *hostname, int port) -{ - SOCKET s; - struct sockaddr_in sin; - - DEBUG(net, 1, "Connecting to %s %d", hostname, port); - - s = socket(AF_INET, SOCK_STREAM, 0); - if (s == INVALID_SOCKET) { - ClientStartError("socket() failed"); - return false; - } - - if (!SetNoDelay(s)) DEBUG(net, 1, "Setting TCP_NODELAY failed"); - - sin.sin_family = AF_INET; - sin.sin_addr.s_addr = NetworkResolveHost(hostname); - sin.sin_port = htons(port); - _network_last_host_ip = sin.sin_addr.s_addr; - - /* We failed to connect for which reason what so ever */ - if (connect(s, (struct sockaddr*) &sin, sizeof(sin)) != 0) return false; - - if (!SetNonBlocking(s)) DEBUG(net, 0, "Setting non-blocking mode failed"); // XXX should this be an error? - - // in client mode, only the first client field is used. it's pointing to the server. - NetworkAllocClient(s); - - _network_join_status = NETWORK_JOIN_STATUS_CONNECTING; - ShowJoinStatusWindow(); - - return true; -} - -// For the server, to accept new clients -static void NetworkAcceptClients(void) -{ - struct sockaddr_in sin; - NetworkClientState *cs; - uint i; - bool banned; - - // Should never ever happen.. is it possible?? - assert(_listensocket != INVALID_SOCKET); - - for (;;) { - socklen_t sin_len = sizeof(sin); - SOCKET s = accept(_listensocket, (struct sockaddr*)&sin, &sin_len); - if (s == INVALID_SOCKET) return; - - SetNonBlocking(s); // XXX error handling? - - DEBUG(net, 1, "Client connected from %s on frame %d", inet_ntoa(sin.sin_addr), _frame_counter); - - SetNoDelay(s); // XXX error handling? - - /* Check if the client is banned */ - banned = false; - for (i = 0; i < lengthof(_network_ban_list); i++) { - if (_network_ban_list[i] == NULL) continue; - - if (sin.sin_addr.s_addr == inet_addr(_network_ban_list[i])) { - Packet *p = NetworkSend_Init(PACKET_SERVER_BANNED); - - DEBUG(net, 1, "Banned ip tried to join (%s), refused", _network_ban_list[i]); - - p->buffer[0] = p->size & 0xFF; - p->buffer[1] = p->size >> 8; - - send(s, p->buffer, p->size, 0); - closesocket(s); - - free(p); - - banned = true; - break; - } - } - /* If this client is banned, continue with next client */ - if (banned) continue; - - cs = NetworkAllocClient(s); - if (cs == NULL) { - // no more clients allowed? - // Send to the client that we are full! - Packet *p = NetworkSend_Init(PACKET_SERVER_FULL); - - p->buffer[0] = p->size & 0xFF; - p->buffer[1] = p->size >> 8; - - send(s, p->buffer, p->size, 0); - closesocket(s); - - free(p); - - continue; - } - - // a new client has connected. We set him at inactive for now - // maybe he is only requesting server-info. Till he has sent a PACKET_CLIENT_MAP_OK - // the client stays inactive - cs->status = STATUS_INACTIVE; - - DEREF_CLIENT_INFO(cs)->client_ip = sin.sin_addr.s_addr; // Save the IP of the client - } -} - -// Set up the listen socket for the server -static bool NetworkListen(void) -{ - SOCKET ls; - struct sockaddr_in sin; - - DEBUG(net, 1, "Listening on %s:%d", _network_server_bind_ip_host, _network_server_port); - - ls = socket(AF_INET, SOCK_STREAM, 0); - if (ls == INVALID_SOCKET) { - ServerStartError("socket() on listen socket failed"); - return false; - } - - { // reuse the socket - int reuse = 1; - // The (const char*) cast is needed for windows!! - if (setsockopt(ls, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse)) == -1) { - ServerStartError("setsockopt() on listen socket failed"); - return false; - } - } - - if (!SetNonBlocking(ls)) DEBUG(net, 0, "Setting non-blocking mode failed"); // XXX should this be an error? - - sin.sin_family = AF_INET; - sin.sin_addr.s_addr = _network_server_bind_ip; - sin.sin_port = htons(_network_server_port); - - if (bind(ls, (struct sockaddr*)&sin, sizeof(sin)) != 0) { - ServerStartError("bind() failed"); - return false; - } - - if (listen(ls, 1) != 0) { - ServerStartError("listen() failed"); - return false; - } - - _listensocket = ls; - - return true; -} - -// Close all current connections -static void NetworkClose(void) -{ - NetworkClientState *cs; - - FOR_ALL_CLIENTS(cs) { - if (!_network_server) { - SEND_COMMAND(PACKET_CLIENT_QUIT)("leaving"); - NetworkSend_Packets(cs); - } - NetworkCloseClient(cs); - } - - if (_network_server) { - // We are a server, also close the listensocket - closesocket(_listensocket); - _listensocket = INVALID_SOCKET; - DEBUG(net, 1, "Closed listener"); - NetworkUDPClose(); - } -} - -// Inits the network (cleans sockets and stuff) -static void NetworkInitialize(void) -{ - NetworkClientState *cs; - - _local_command_queue = NULL; - - // Clean all client-sockets - memset(_clients, 0, sizeof(_clients)); - for (cs = _clients; cs != &_clients[MAX_CLIENTS]; cs++) { - cs->socket = INVALID_SOCKET; - cs->status = STATUS_INACTIVE; - cs->command_queue = NULL; - } - - // Clean the client_info memory - memset(&_network_client_info, 0, sizeof(_network_client_info)); - memset(&_network_player_info, 0, sizeof(_network_player_info)); - - _sync_frame = 0; - _network_first_time = true; - - _network_reconnect = 0; - - NetworkUDPInitialize(); -} - -// Query a server to fetch his game-info -// If game_info is true, only the gameinfo is fetched, -// else only the client_info is fetched -NetworkGameList *NetworkQueryServer(const char* host, unsigned short port, bool game_info) -{ - if (!_network_available) return NULL; - - NetworkDisconnect(); - - if (game_info) return NetworkUDPQueryServer(host, port); - - NetworkInitialize(); - - _network_server = false; - - // Try to connect - _networking = NetworkConnect(host, port); - - // We are connected - if (_networking) { - SEND_COMMAND(PACKET_CLIENT_COMPANY_INFO)(); - } else { // No networking, close everything down again - NetworkDisconnect(); - } - - return NULL; -} - -/* Validates an address entered as a string and adds the server to - * the list. If you use this function, the games will be marked - * as manually added. */ -void NetworkAddServer(const char *b) -{ - if (*b != '\0') { - NetworkGameList *item; - const char *port = NULL; - const char *player = NULL; - char host[NETWORK_HOSTNAME_LENGTH]; - uint16 rport; - - ttd_strlcpy(host, b, lengthof(host)); - - ttd_strlcpy(_network_default_ip, b, lengthof(_network_default_ip)); - rport = NETWORK_DEFAULT_PORT; - - ParseConnectionString(&player, &port, host); - if (port != NULL) rport = atoi(port); - - item = NetworkQueryServer(host, rport, true); - item->manually = true; - } -} - -/* Generates the list of manually added hosts from NetworkGameList and - * dumps them into the array _network_host_list. This array is needed - * by the function that generates the config file. */ -void NetworkRebuildHostList(void) -{ - uint i = 0; - const NetworkGameList *item = _network_game_list; - while (item != NULL && i != lengthof(_network_host_list)) { - if (item->manually) { - free(_network_host_list[i]); - _network_host_list[i++] = str_fmt("%s:%i", item->info.hostname, item->port); - } - item = item->next; - } - - for (; i < lengthof(_network_host_list); i++) { - free(_network_host_list[i]); - _network_host_list[i] = NULL; - } -} - -// Used by clients, to connect to a server -bool NetworkClientConnectGame(const char *host, uint16 port) -{ - if (!_network_available) return false; - - if (port == 0) return false; - - ttd_strlcpy(_network_last_host, host, sizeof(_network_last_host)); - _network_last_port = port; - - NetworkDisconnect(); - NetworkUDPClose(); - NetworkInitialize(); - - // Try to connect - _networking = NetworkConnect(host, port); - - // We are connected - if (_networking) { - IConsoleCmdExec("exec scripts/on_client.scr 0"); - NetworkClient_Connected(); - } else { - // Connecting failed - NetworkError(STR_NETWORK_ERR_NOCONNECTION); - } - - return _networking; -} - -static void NetworkInitGameInfo(void) -{ - NetworkClientInfo *ci; - - ttd_strlcpy(_network_game_info.server_name, _network_server_name, sizeof(_network_game_info.server_name)); - ttd_strlcpy(_network_game_info.server_password, _network_server_password, sizeof(_network_server_password)); - ttd_strlcpy(_network_game_info.rcon_password, _network_rcon_password, sizeof(_network_rcon_password)); - if (_network_game_info.server_name[0] == '\0') - snprintf(_network_game_info.server_name, sizeof(_network_game_info.server_name), "Unnamed Server"); - - ttd_strlcpy(_network_game_info.server_revision, _openttd_revision, sizeof(_network_game_info.server_revision)); - - // The server is a client too ;) - if (_network_dedicated) { - _network_game_info.clients_on = 0; - _network_game_info.companies_on = 0; - _network_game_info.dedicated = true; - } else { - _network_game_info.clients_on = 1; - _network_game_info.companies_on = 1; - _network_game_info.dedicated = false; - } - - _network_game_info.spectators_on = 0; - - _network_game_info.game_date = _date; - _network_game_info.start_date = ConvertYMDToDate(_patches.starting_year, 0, 1); - _network_game_info.map_width = MapSizeX(); - _network_game_info.map_height = MapSizeY(); - _network_game_info.map_set = _opt.landscape; - - _network_game_info.use_password = (_network_server_password[0] != '\0'); - - // We use _network_client_info[MAX_CLIENT_INFO - 1] to store the server-data in it - // The index is NETWORK_SERVER_INDEX ( = 1) - ci = &_network_client_info[MAX_CLIENT_INFO - 1]; - memset(ci, 0, sizeof(*ci)); - - ci->client_index = NETWORK_SERVER_INDEX; - ci->client_playas = _network_dedicated ? PLAYER_SPECTATOR : _local_player; - - ttd_strlcpy(ci->client_name, _network_player_name, sizeof(ci->client_name)); - ttd_strlcpy(ci->unique_id, _network_unique_id, sizeof(ci->unique_id)); -} - -bool NetworkServerStart(void) -{ - if (!_network_available) return false; - - /* Call the pre-scripts */ - IConsoleCmdExec("exec scripts/pre_server.scr 0"); - if (_network_dedicated) IConsoleCmdExec("exec scripts/pre_dedicated.scr 0"); - - NetworkInitialize(); - if (!NetworkListen()) return false; - - // Try to start UDP-server - _network_udp_server = true; - _network_udp_server = NetworkUDPListen(&_udp_server_socket, _network_server_bind_ip, _network_server_port, false); - - _network_server = true; - _networking = true; - _frame_counter = 0; - _frame_counter_server = 0; - _frame_counter_max = 0; - _last_sync_frame = 0; - _network_own_client_index = NETWORK_SERVER_INDEX; - - /* Non-dedicated server will always be player #1 */ - if (!_network_dedicated) _network_playas = 0; - - _network_clients_connected = 0; - - NetworkInitGameInfo(); - - // execute server initialization script - IConsoleCmdExec("exec scripts/on_server.scr 0"); - // if the server is dedicated ... add some other script - if (_network_dedicated) IConsoleCmdExec("exec scripts/on_dedicated.scr 0"); - - _min_players_paused = false; - CheckMinPlayers(); - - /* Try to register us to the master server */ - _network_last_advertise_frame = 0; - _network_need_advertise = true; - NetworkUDPAdvertise(); - return true; -} - -// The server is rebooting... -// The only difference with NetworkDisconnect, is the packets that is sent -void NetworkReboot(void) -{ - if (_network_server) { - NetworkClientState *cs; - FOR_ALL_CLIENTS(cs) { - SEND_COMMAND(PACKET_SERVER_NEWGAME)(cs); - NetworkSend_Packets(cs); - } - } - - NetworkClose(); - - // Free all queued commands - while (_local_command_queue != NULL) { - CommandPacket *p = _local_command_queue; - _local_command_queue = _local_command_queue->next; - free(p); - } - - _networking = false; - _network_server = false; -} - -// We want to disconnect from the host/clients -void NetworkDisconnect(void) -{ - if (_network_server) { - NetworkClientState *cs; - FOR_ALL_CLIENTS(cs) { - SEND_COMMAND(PACKET_SERVER_SHUTDOWN)(cs); - NetworkSend_Packets(cs); - } - } - - if (_network_advertise) NetworkUDPRemoveAdvertise(); - - DeleteWindowById(WC_NETWORK_STATUS_WINDOW, 0); - - NetworkClose(); - - // Free all queued commands - while (_local_command_queue != NULL) { - CommandPacket *p = _local_command_queue; - _local_command_queue = _local_command_queue->next; - free(p); - } - - _networking = false; - _network_server = false; -} - -// Receives something from the network -static bool NetworkReceive(void) -{ - NetworkClientState *cs; - int n; - fd_set read_fd, write_fd; - struct timeval tv; - - FD_ZERO(&read_fd); - FD_ZERO(&write_fd); - - FOR_ALL_CLIENTS(cs) { - FD_SET(cs->socket, &read_fd); - FD_SET(cs->socket, &write_fd); - } - - // take care of listener port - if (_network_server) FD_SET(_listensocket, &read_fd); - - tv.tv_sec = tv.tv_usec = 0; // don't block at all. -#if !defined(__MORPHOS__) && !defined(__AMIGA__) - n = select(FD_SETSIZE, &read_fd, &write_fd, NULL, &tv); -#else - n = WaitSelect(FD_SETSIZE, &read_fd, &write_fd, NULL, &tv, NULL); -#endif - if (n == -1 && !_network_server) NetworkError(STR_NETWORK_ERR_LOSTCONNECTION); - - // accept clients.. - if (_network_server && FD_ISSET(_listensocket, &read_fd)) NetworkAcceptClients(); - - // read stuff from clients - FOR_ALL_CLIENTS(cs) { - cs->writable = !!FD_ISSET(cs->socket, &write_fd); - if (FD_ISSET(cs->socket, &read_fd)) { - if (_network_server) { - NetworkServer_ReadPackets(cs); - } else { - NetworkRecvStatus res; - - // The client already was quiting! - if (cs->has_quit) return false; - - res = NetworkClient_ReadPackets(cs); - if (res != NETWORK_RECV_STATUS_OKAY) { - // The client made an error of which we can not recover - // close the client and drop back to main menu - NetworkClientError(res, cs); - return false; - } - } - } - } - return true; -} - -// This sends all buffered commands (if possible) -static void NetworkSend(void) -{ - NetworkClientState *cs; - FOR_ALL_CLIENTS(cs) { - if (cs->writable) { - NetworkSend_Packets(cs); - - if (cs->status == STATUS_MAP) { - // This client is in the middle of a map-send, call the function for that - SEND_COMMAND(PACKET_SERVER_MAP)(cs); - } - } - } -} - -// Handle the local-command-queue -static void NetworkHandleLocalQueue(void) -{ - CommandPacket *cp, **cp_prev; - - cp_prev = &_local_command_queue; - - while ( (cp = *cp_prev) != NULL) { - - // The queue is always in order, which means - // that the first element will be executed first. - if (_frame_counter < cp->frame) break; - - if (_frame_counter > cp->frame) { - // If we reach here, it means for whatever reason, we've already executed - // past the command we need to execute. - DEBUG(net, 0, "Trying to execute a packet in the past!"); - assert(0); - } - - // We can execute this command - NetworkExecuteCommand(cp); - - *cp_prev = cp->next; - free(cp); - } - - // Just a safety check, to be removed in the future. - // Make sure that no older command appears towards the end of the queue - // In that case we missed executing it. This will never happen. - for (cp = _local_command_queue; cp; cp = cp->next) { - assert(_frame_counter < cp->frame); - } - -} - -static bool NetworkDoClientLoop(void) -{ - _frame_counter++; - - NetworkHandleLocalQueue(); - - StateGameLoop(); - - // Check if we are in sync! - if (_sync_frame != 0) { - if (_sync_frame == _frame_counter) { -#ifdef NETWORK_SEND_DOUBLE_SEED - if (_sync_seed_1 != _random_seeds[0][0] || _sync_seed_2 != _random_seeds[0][1]) { -#else - if (_sync_seed_1 != _random_seeds[0][0]) { -#endif - NetworkError(STR_NETWORK_ERR_DESYNC); - DEBUG(net, 0, "Sync error detected!"); - NetworkClientError(NETWORK_RECV_STATUS_DESYNC, DEREF_CLIENT(0)); - return false; - } - - // If this is the first time we have a sync-frame, we - // need to let the server know that we are ready and at the same - // frame as he is.. so we can start playing! - if (_network_first_time) { - _network_first_time = false; - SEND_COMMAND(PACKET_CLIENT_ACK)(); - } - - _sync_frame = 0; - } else if (_sync_frame < _frame_counter) { - DEBUG(net, 1, "Missed frame for sync-test (%d / %d)", _sync_frame, _frame_counter); - _sync_frame = 0; - } - } - - return true; -} - -// We have to do some UDP checking -void NetworkUDPGameLoop(void) -{ - if (_network_udp_server) { - NetworkUDPReceive(_udp_server_socket); - if (_udp_master_socket != INVALID_SOCKET) { - NetworkUDPReceive(_udp_master_socket); - } - } else if (_udp_client_socket != INVALID_SOCKET) { - NetworkUDPReceive(_udp_client_socket); - if (_network_udp_broadcast > 0) _network_udp_broadcast--; - } -} - -// The main loop called from ttd.c -// Here we also have to do StateGameLoop if needed! -void NetworkGameLoop(void) -{ - if (!_networking) return; - - if (!NetworkReceive()) return; - - if (_network_server) { - bool send_frame = false; - - // We first increase the _frame_counter - _frame_counter++; - // Update max-frame-counter - if (_frame_counter > _frame_counter_max) { - _frame_counter_max = _frame_counter + _network_frame_freq; - send_frame = true; - } - - NetworkHandleLocalQueue(); - - // Then we make the frame - StateGameLoop(); - - _sync_seed_1 = _random_seeds[0][0]; -#ifdef NETWORK_SEND_DOUBLE_SEED - _sync_seed_2 = _random_seeds[0][1]; -#endif - - NetworkServer_Tick(send_frame); - } else { - // Client - - // Make sure we are at the frame were the server is (quick-frames) - if (_frame_counter_server > _frame_counter) { - while (_frame_counter_server > _frame_counter) { - if (!NetworkDoClientLoop()) break; - } - } else { - // Else, keep on going till _frame_counter_max - if (_frame_counter_max > _frame_counter) NetworkDoClientLoop(); - } - } - - NetworkSend(); -} - -static void NetworkGenerateUniqueId(void) -{ - md5_state_t state; - md5_byte_t digest[16]; - char hex_output[16*2 + 1]; - char coding_string[NETWORK_NAME_LENGTH]; - int di; - - snprintf(coding_string, sizeof(coding_string), "%d%s", (uint)Random(), "OpenTTD Unique ID"); - - /* Generate the MD5 hash */ - md5_init(&state); - md5_append(&state, (const md5_byte_t*)coding_string, strlen(coding_string)); - md5_finish(&state, digest); - - for (di = 0; di < 16; ++di) - sprintf(hex_output + di * 2, "%02x", digest[di]); - - /* _network_unique_id is our id */ - snprintf(_network_unique_id, sizeof(_network_unique_id), "%s", hex_output); -} - -// This tries to launch the network for a given OS -void NetworkStartUp(void) -{ - DEBUG(net, 3, "[core] starting network..."); - -#if defined(__MORPHOS__) || defined(__AMIGA__) - /* - * IMPORTANT NOTE: SocketBase needs to be initialized before we use _any_ - * network related function, else: crash. - */ - DEBUG(net, 3, "[core] loading bsd socket library"); - SocketBase = OpenLibrary("bsdsocket.library", 4); - if (SocketBase == NULL) { - DEBUG(net, 0, "[core] can't open bsdsocket.library version 4, network unavailable"); - _network_available = false; - return; - } - -#if defined(__AMIGA__) - // for usleep() implementation (only required for legacy AmigaOS builds) - TimerPort = CreateMsgPort(); - if (TimerPort != NULL) { - TimerRequest = (struct timerequest*)CreateIORequest(TimerPort, sizeof(struct timerequest); - if (TimerRequest != NULL) { - if (OpenDevice("timer.device", UNIT_MICROHZ, (struct IORequest*)TimerRequest, 0) == 0) { - TimerBase = TimerRequest->tr_node.io_Device; - if (TimerBase == NULL) { - // free ressources... - DEBUG(net, 0, "[core] can't initialize timer, network unavailable"); - _network_available = false; - return; - } - } - } - } -#endif // __AMIGA__ -#endif // __MORPHOS__ / __AMIGA__ - - // Network is available - _network_available = true; - _network_dedicated = false; - _network_last_advertise_frame = 0; - _network_need_advertise = true; - _network_advertise_retries = 0; - - /* Load the ip from the openttd.cfg */ - _network_server_bind_ip = inet_addr(_network_server_bind_ip_host); - /* And put the data back in it in case it was an invalid ip */ - snprintf(_network_server_bind_ip_host, sizeof(_network_server_bind_ip_host), "%s", inet_ntoa(*(struct in_addr *)&_network_server_bind_ip)); - - /* Generate an unique id when there is none yet */ - if (_network_unique_id[0] == '\0') NetworkGenerateUniqueId(); - - { - byte cl_max = _network_game_info.clients_max; - byte cp_max = _network_game_info.companies_max; - byte sp_max = _network_game_info.spectators_max; - - memset(&_network_game_info, 0, sizeof(_network_game_info)); - _network_game_info.clients_max = cl_max; - _network_game_info.companies_max = cp_max; - _network_game_info.spectators_max = sp_max; - } - - // Let's load the network in windows - #if defined(WIN32) - { - WSADATA wsa; - DEBUG(net, 3, "[core] loading windows socket library"); - if (WSAStartup(MAKEWORD(2,0), &wsa) != 0) { - DEBUG(net, 0, "[core] WSAStartup failed, network unavailable"); - _network_available = false; - return; - } - } - #endif // WIN32 - - NetworkInitialize(); - DEBUG(net, 3, "[core] network online, multiplayer available"); - NetworkFindIPs(); -} - -// This shuts the network down -void NetworkShutDown(void) -{ - NetworkDisconnect(); - NetworkUDPClose(); - - DEBUG(net, 3, "[core] shutting down network"); - - _network_available = false; - -#if defined(__MORPHOS__) || defined(__AMIGA__) - // free allocated ressources -#if defined(__AMIGA__) - if (TimerBase != NULL) CloseDevice((struct IORequest*)TimerRequest); // XXX This smells wrong - if (TimerRequest != NULL) DeleteIORequest(TimerRequest); - if (TimerPort != NULL) DeleteMsgPort(TimerPort); -#endif - - if (SocketBase != NULL) CloseLibrary(SocketBase); -#endif - -#if defined(WIN32) - WSACleanup(); -#endif -} - -#endif /* ENABLE_NETWORK */