rubidium@5720: /* $Id$ */ rubidium@5720: rubidium@5720: #ifdef ENABLE_NETWORK rubidium@5720: rubidium@5720: #include "../../stdafx.h" rubidium@5720: #include "../../debug.h" rubidium@5720: #include "../../macros.h" rubidium@5838: #include "../../helpers.hpp" rubidium@5720: #include "packet.h" rubidium@5720: #include "udp.h" rubidium@5720: rubidium@5720: /** rubidium@5720: * @file udp.c Basic functions to receive and send UDP packets. rubidium@5720: */ rubidium@5720: rubidium@5720: /** rubidium@5720: * Start listening on the given host and port. rubidium@5720: * @param udp the place where the (references to the) UDP are stored rubidium@5720: * @param host the host (ip) to listen on rubidium@5720: * @param port the port to listen on rubidium@5720: * @param broadcast whether to allow broadcast sending/receiving rubidium@5720: * @return true if the listening succeeded rubidium@5720: */ rubidium@5779: bool NetworkUDPListen(SOCKET *udp, const uint32 host, const uint16 port, const bool broadcast) rubidium@5720: { rubidium@5720: struct sockaddr_in sin; rubidium@5720: rubidium@5720: /* Make sure socket is closed */ rubidium@5782: NetworkUDPClose(udp); rubidium@5720: rubidium@5720: *udp = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); rubidium@5720: if (*udp == INVALID_SOCKET) { rubidium@5720: DEBUG(net, 0, "[udp] failed to start UDP listener"); rubidium@5720: return false; rubidium@5720: } rubidium@5720: rubidium@5720: /* set nonblocking mode for socket */ rubidium@5720: { rubidium@5720: unsigned long blocking = 1; rubidium@5720: #ifndef BEOS_NET_SERVER rubidium@5720: ioctlsocket(*udp, FIONBIO, &blocking); rubidium@5720: #else rubidium@5720: setsockopt(*udp, SOL_SOCKET, SO_NONBLOCK, &blocking, NULL); rubidium@5720: #endif rubidium@5720: } rubidium@5720: rubidium@5720: sin.sin_family = AF_INET; rubidium@5720: /* Listen on all IPs */ rubidium@5720: sin.sin_addr.s_addr = host; rubidium@5720: sin.sin_port = htons(port); rubidium@5720: rubidium@5720: if (bind(*udp, (struct sockaddr*)&sin, sizeof(sin)) != 0) { rubidium@5720: DEBUG(net, 0, "[udp] bind failed on %s:%i", inet_ntoa(*(struct in_addr *)&host), port); rubidium@5720: return false; rubidium@5720: } rubidium@5720: rubidium@5720: if (broadcast) { rubidium@5720: /* Enable broadcast */ rubidium@5720: unsigned long val = 1; rubidium@5720: #ifndef BEOS_NET_SERVER // will work around this, some day; maybe. rubidium@5720: setsockopt(*udp, SOL_SOCKET, SO_BROADCAST, (char *) &val , sizeof(val)); rubidium@5720: #endif rubidium@5720: } rubidium@5720: rubidium@5720: DEBUG(net, 1, "[udp] listening on port %s:%d", inet_ntoa(*(struct in_addr *)&host), port); rubidium@5720: rubidium@5720: return true; rubidium@5720: } rubidium@5720: rubidium@5720: /** rubidium@5772: * Close the given UDP socket rubidium@5772: * @param udp the socket to close rubidium@5772: */ rubidium@5772: void NetworkUDPClose(SOCKET *udp) rubidium@5772: { rubidium@5772: if (*udp == INVALID_SOCKET) return; rubidium@5772: rubidium@5772: closesocket(*udp); rubidium@5772: *udp = INVALID_SOCKET; rubidium@5772: } rubidium@5772: rubidium@5772: rubidium@5772: /** rubidium@5772: * Send a packet over UDP rubidium@5772: * @param udp the socket to send over rubidium@5772: * @param p the packet to send rubidium@5772: * @param recv the receiver (target) of the packet rubidium@5772: */ rubidium@5779: void NetworkSendUDP_Packet(const SOCKET udp, Packet *p, const struct sockaddr_in *recv) rubidium@5772: { rubidium@5772: int res; rubidium@5772: rubidium@5772: NetworkSend_FillPacketSize(p); rubidium@5772: rubidium@5772: /* Send the buffer */ rubidium@5838: res = sendto(udp, (const char*)p->buffer, p->size, 0, (struct sockaddr *)recv, sizeof(*recv)); rubidium@5772: rubidium@5772: /* Check for any errors, but ignore it otherwise */ rubidium@5772: if (res == -1) DEBUG(net, 1, "[udp] sendto failed with: %i", GET_LAST_ERROR()); rubidium@5772: } rubidium@5772: rubidium@5772: /** rubidium@5720: * Receive a packet at UDP level rubidium@5720: * @param udp the socket to receive the packet on rubidium@5720: */ rubidium@5779: void NetworkUDPReceive(const SOCKET udp) rubidium@5720: { rubidium@5720: struct sockaddr_in client_addr; rubidium@5720: socklen_t client_len; rubidium@5720: int nbytes; rubidium@5720: Packet p; rubidium@5720: int packet_len; rubidium@5720: rubidium@5720: packet_len = sizeof(p.buffer); rubidium@5720: client_len = sizeof(client_addr); rubidium@5720: rubidium@5720: /* Try to receive anything */ rubidium@5838: nbytes = recvfrom(udp, (char*)p.buffer, packet_len, 0, (struct sockaddr *)&client_addr, &client_len); rubidium@5720: rubidium@5823: /* We got some bytes for the base header of the packet. */ rubidium@5720: if (nbytes > 2) { rubidium@5720: NetworkRecv_ReadPacketSize(&p); rubidium@5720: rubidium@5823: /* If the size does not match the packet must be corrupted. rubidium@5823: * Otherwise it will be marked as corrupted later on. */ rubidium@5823: if (nbytes != p.size) { rubidium@5823: DEBUG(net, 1, "received a packet with mismatching size from %s:%d", rubidium@5823: inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); rubidium@5823: rubidium@5823: return; rubidium@5823: } rubidium@5823: rubidium@5720: /* Put the position on the right place */ rubidium@5720: p.pos = 2; rubidium@5720: p.next = NULL; rubidium@5720: rubidium@5720: /* Handle the packet */ rubidium@5770: NetworkHandleUDPPacket(udp, &p, &client_addr); rubidium@5720: } rubidium@5720: } rubidium@5720: rubidium@5720: rubidium@5720: /** rubidium@5720: * Serializes the GRFIdentifier (GRF ID and MD5 checksum) to the packet rubidium@5720: * @param p the packet to write the data to rubidium@5720: * @param c the configuration to write the GRF ID and MD5 checksum from rubidium@5720: */ rubidium@5720: void NetworkSend_GRFIdentifier(Packet *p, const GRFConfig *c) rubidium@5720: { rubidium@5720: uint j; rubidium@5720: NetworkSend_uint32(p, c->grfid); rubidium@5720: for (j = 0; j < sizeof(c->md5sum); j++) { rubidium@5720: NetworkSend_uint8 (p, c->md5sum[j]); rubidium@5720: } rubidium@5720: } rubidium@5720: rubidium@5720: /** rubidium@5720: * Deserializes the GRFIdentifier (GRF ID and MD5 checksum) from the packet rubidium@5720: * @param cs the client state (for closing connect on out-of-bounds reading etc) rubidium@5720: * @param p the packet to read the data from rubidium@5720: * @param c the configuration to write the GRF ID and MD5 checksum to rubidium@5720: */ rubidium@5720: void NetworkRecv_GRFIdentifier(NetworkClientState *cs, Packet *p, GRFConfig *c) rubidium@5720: { rubidium@5720: uint j; rubidium@5720: c->grfid = NetworkRecv_uint32(cs, p); rubidium@5720: for (j = 0; j < sizeof(c->md5sum); j++) { rubidium@5720: c->md5sum[j] = NetworkRecv_uint8(cs, p); rubidium@5720: } rubidium@5720: } rubidium@5720: rubidium@5720: rubidium@5720: /** rubidium@5720: * Serializes the NetworkGameInfo struct to the packet rubidium@5720: * @param p the packet to write the data to rubidium@5720: * @param info the NetworkGameInfo struct to serialize rubidium@5720: */ rubidium@5720: void NetworkSend_NetworkGameInfo(Packet *p, const NetworkGameInfo *info) rubidium@5720: { rubidium@5720: NetworkSend_uint8 (p, NETWORK_GAME_INFO_VERSION); rubidium@5720: rubidium@5720: /* rubidium@5720: * Please observe the order. rubidium@5720: * The parts must be read in the same order as they are sent! rubidium@5720: */ rubidium@5720: rubidium@5796: /* Update the documentation in udp.h on changes rubidium@5796: * to the NetworkGameInfo wire-protocol! */ rubidium@5720: rubidium@5720: /* NETWORK_GAME_INFO_VERSION = 4 */ rubidium@5720: { rubidium@5720: /* Only send the GRF Identification (GRF_ID and MD5 checksum) of rubidium@5720: * the GRFs that are needed, i.e. the ones that the server has rubidium@5720: * selected in the NewGRF GUI and not the ones that are used due rubidium@5720: * to the fact that they are in [newgrf-static] in openttd.cfg */ rubidium@5720: const GRFConfig *c; rubidium@5720: uint count = 0; rubidium@5720: rubidium@5720: /* Count number of GRFs to send information about */ rubidium@5720: for (c = info->grfconfig; c != NULL; c = c->next) { rubidium@5720: if (!HASBIT(c->flags, GCF_STATIC)) count++; rubidium@5720: } rubidium@5720: NetworkSend_uint8 (p, count); // Send number of GRFs rubidium@5720: rubidium@5720: /* Send actual GRF Identifications */ rubidium@5720: for (c = info->grfconfig; c != NULL; c = c->next) { rubidium@5720: if (!HASBIT(c->flags, GCF_STATIC)) NetworkSend_GRFIdentifier(p, c); rubidium@5720: } rubidium@5720: } rubidium@5720: rubidium@5720: /* NETWORK_GAME_INFO_VERSION = 3 */ rubidium@5720: NetworkSend_uint32(p, info->game_date); rubidium@5720: NetworkSend_uint32(p, info->start_date); rubidium@5720: rubidium@5720: /* NETWORK_GAME_INFO_VERSION = 2 */ rubidium@5720: NetworkSend_uint8 (p, info->companies_max); rubidium@5720: NetworkSend_uint8 (p, info->companies_on); rubidium@5720: NetworkSend_uint8 (p, info->spectators_max); rubidium@5720: rubidium@5720: /* NETWORK_GAME_INFO_VERSION = 1 */ rubidium@5720: NetworkSend_string(p, info->server_name); rubidium@5720: NetworkSend_string(p, info->server_revision); rubidium@5720: NetworkSend_uint8 (p, info->server_lang); rubidium@5720: NetworkSend_uint8 (p, info->use_password); rubidium@5720: NetworkSend_uint8 (p, info->clients_max); rubidium@5720: NetworkSend_uint8 (p, info->clients_on); rubidium@5720: NetworkSend_uint8 (p, info->spectators_on); rubidium@5720: NetworkSend_string(p, info->map_name); rubidium@5720: NetworkSend_uint16(p, info->map_width); rubidium@5720: NetworkSend_uint16(p, info->map_height); rubidium@5720: NetworkSend_uint8 (p, info->map_set); rubidium@5720: NetworkSend_uint8 (p, info->dedicated); rubidium@5720: } rubidium@5720: rubidium@5720: /** rubidium@5720: * Deserializes the NetworkGameInfo struct from the packet rubidium@5720: * @param cs the client state (for closing connect on out-of-bounds reading etc) rubidium@5720: * @param p the packet to read the data from rubidium@5720: * @param info the NetworkGameInfo to deserialize into rubidium@5720: */ rubidium@5720: void NetworkRecv_NetworkGameInfo(NetworkClientState *cs, Packet *p, NetworkGameInfo *info) rubidium@5720: { rubidium@5720: info->game_info_version = NetworkRecv_uint8(cs, p); rubidium@5720: rubidium@5720: /* rubidium@5720: * Please observe the order. rubidium@5720: * The parts must be read in the same order as they are sent! rubidium@5720: */ rubidium@5720: rubidium@5796: /* Update the documentation in udp.h on changes rubidium@5796: * to the NetworkGameInfo wire-protocol! */ rubidium@5796: rubidium@5720: switch (info->game_info_version) { rubidium@5720: case 4: { KUDr@5860: GRFConfig **dst = &info->grfconfig; rubidium@5720: uint i; rubidium@5720: uint num_grfs = NetworkRecv_uint8(cs, p); rubidium@5720: rubidium@5720: for (i = 0; i < num_grfs; i++) { KUDr@5860: GRFConfig *c = CallocT(1); rubidium@5720: NetworkRecv_GRFIdentifier(cs, p, c); rubidium@5720: HandleIncomingNetworkGameInfoGRFConfig(c); rubidium@5720: rubidium@5720: /* Append GRFConfig to the list */ rubidium@5720: *dst = c; rubidium@5720: dst = &c->next; rubidium@5720: } rubidium@5720: } /* Fallthrough */ rubidium@5720: case 3: rubidium@5720: info->game_date = NetworkRecv_uint32(cs, p); rubidium@5720: info->start_date = NetworkRecv_uint32(cs, p); rubidium@5720: /* Fallthrough */ rubidium@5720: case 2: rubidium@5720: info->companies_max = NetworkRecv_uint8 (cs, p); rubidium@5720: info->companies_on = NetworkRecv_uint8 (cs, p); rubidium@5720: info->spectators_max = NetworkRecv_uint8 (cs, p); rubidium@5720: /* Fallthrough */ rubidium@5720: case 1: rubidium@5720: NetworkRecv_string(cs, p, info->server_name, sizeof(info->server_name)); rubidium@5720: NetworkRecv_string(cs, p, info->server_revision, sizeof(info->server_revision)); rubidium@5720: info->server_lang = NetworkRecv_uint8 (cs, p); rubidium@5720: info->use_password = NetworkRecv_uint8 (cs, p); rubidium@5720: info->clients_max = NetworkRecv_uint8 (cs, p); rubidium@5720: info->clients_on = NetworkRecv_uint8 (cs, p); rubidium@5720: info->spectators_on = NetworkRecv_uint8 (cs, p); rubidium@5720: if (info->game_info_version < 3) { // 16 bits dates got scrapped and are read earlier rubidium@5720: info->game_date = NetworkRecv_uint16(cs, p) + DAYS_TILL_ORIGINAL_BASE_YEAR; rubidium@5720: info->start_date = NetworkRecv_uint16(cs, p) + DAYS_TILL_ORIGINAL_BASE_YEAR; rubidium@5720: } rubidium@5720: NetworkRecv_string(cs, p, info->map_name, sizeof(info->map_name)); rubidium@5720: info->map_width = NetworkRecv_uint16(cs, p); rubidium@5720: info->map_height = NetworkRecv_uint16(cs, p); rubidium@5720: info->map_set = NetworkRecv_uint8 (cs, p); rubidium@5838: info->dedicated = (NetworkRecv_uint8 (cs, p) != 0); rubidium@5720: } rubidium@5720: } rubidium@5720: rubidium@5720: #endif /* ENABLE_NETWORK */