// Emacs style mode select -*- C++ -*- //----------------------------------------------------------------------------- // // $Id$ // // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 2006-2026 by The Odamex Team. // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // DESCRIPTION: // System specific network interface stuff. // //----------------------------------------------------------------------------- #include "odamex.h" /* [Petteri] Check if compiling for Win32: */ //#if defined(__WINDOWS__) || defined(__NT__) || defined(_MSC_VER) || defined(WIN32) //# define WIN32 //#endif /* Follow #ifdef __WIN32__ marks */ #include #include /* [Petteri] Use Winsock for Win32: */ #include "win32inc.h" #ifdef _WIN32 #include #include #else #include #include #include #include #include #include #include #include #include #endif // WIN32 #ifndef _WIN32 typedef int SOCKET; #define SOCKET_ERROR -1 #define INVALID_SOCKET -1 #define closesocket close #define ioctlsocket ioctl #endif #ifdef _WIN32 #define SETSOCKOPTCAST(x) ((const char *)(x)) #else #define SETSOCKOPTCAST(x) ((const void *)(x)) #endif #include #include "i_system.h" #include "i_net.h" #include "svc_map.h" #include "d_player.h" #include "m_alloc.h" #include "minilzo.h" #ifdef ODA_HAVE_MINIUPNP #include "miniupnpc/miniwget.h" #include "miniupnpc/miniupnpc.h" #include "miniupnpc/upnpcommands.h" #endif unsigned int inet_socket; int localport; netadr_t net_from; // address of who sent the packet buf_t net_message(MAX_UDP_PACKET); extern bool simulated_connection; // buffer for compression/decompression // can't be static to a function because some // of the functions buf_t compressed, decompressed; lzo_byte wrkmem[LZO1X_1_MEM_COMPRESS]; EXTERN_CVAR(port) msg_info_t clc_info[clc_max + 1]; msg_info_t svc_info[svc_max + 1]; #ifdef ODA_HAVE_MINIUPNP EXTERN_CVAR(sv_upnp) EXTERN_CVAR(sv_upnp_discovertimeout) EXTERN_CVAR(sv_upnp_description) EXTERN_CVAR(sv_upnp_internalip) EXTERN_CVAR(sv_upnp_externalip) static struct UPNPUrls urls; static struct IGDdatas data; static bool is_upnp_ok = false; void init_upnp (void) { struct UPNPDev * devlist; struct UPNPDev * dev; char * descXML; int descXMLsize = 0; int res = 0; char IPAddress[40]; int r; if (!sv_upnp) return; memset(&urls, 0, sizeof(struct UPNPUrls)); memset(&data, 0, sizeof(struct IGDdatas)); PrintFmt(PRINT_HIGH, "UPnP: Discovering router (max 1 unit supported)\n"); #if MINIUPNPC_API_VERSION < 14 devlist = upnpDiscover(sv_upnp_discovertimeout.asInt(), NULL, NULL, 0, 0, &res); #else devlist = upnpDiscover(sv_upnp_discovertimeout.asInt(), NULL, NULL, 0, 0, 2, &res); #endif if (!devlist || res != UPNPDISCOVER_SUCCESS) { PrintFmt(PRINT_WARNING, "UPnP: Router not found or timed out, error {}\n", res); is_upnp_ok = false; return; } dev = devlist; while (dev) { if (strstr (dev->st, "InternetGatewayDevice")) break; dev = dev->pNext; } if (!dev) dev = devlist; /* defaulting to first device */ //Printf(PRINT_HIGH, "UPnP device :\n" // " desc: %s\n st: %s\n", // dev->descURL, dev->st); #if MINIUPNPC_API_VERSION < 16 descXML = (char *)miniwget(dev->descURL, &descXMLsize, 0); #else descXML = (char *)miniwget(dev->descURL, &descXMLsize, 0, &res); #endif if (descXML) { parserootdesc (descXML, descXMLsize, &data); M_Free(descXML); GetUPNPUrls (&urls, &data, dev->descURL, 0); } freeUPNPDevlist(devlist); r = UPNP_GetExternalIPAddress(urls.controlURL, data.first.servicetype, IPAddress); if (r != 0) { PrintFmt(PRINT_HIGH, "UPnP: Router found but unable to get external IP address\n"); is_upnp_ok = false; } else { PrintFmt(PRINT_HIGH, "UPnP: Router found, external IP address is: {}\n", IPAddress); // Store ip address just in case admin wants it sv_upnp_externalip.ForceSet(IPAddress); is_upnp_ok = true; } } void upnp_add_redir (const char * addr, int port) { if (!sv_upnp || !is_upnp_ok) return; if (urls.controlURL == NULL) return; const std::string port_str = fmt::format("{}", port); // Set a description if none exists if (!sv_upnp_description.cstring()[0]) { std::stringstream desc; desc << "Odasrv " << "(" << addr << ":" << port_str << ")"; sv_upnp_description.Set(desc.str().c_str()); } const int r = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype, port_str.c_str(), port_str.c_str(), addr, sv_upnp_description.cstring(), "UDP", NULL, 0); if (r != 0) { PrintFmt(PRINT_HIGH, "UPnP: AddPortMapping failed: {}\n", r); is_upnp_ok = false; } else { PrintFmt(PRINT_HIGH, "UPnP: Port mapping added to router: {}", sv_upnp_description.str()); is_upnp_ok = true; } } void upnp_rem_redir (int port) { if (!is_upnp_ok) return; if(urls.controlURL == NULL) return; const std::string port_str = fmt::format("{}", port); const int r = UPNP_DeletePortMapping(urls.controlURL, data.first.servicetype, port_str.c_str(), "UDP", 0); if (r != 0) { PrintFmt(PRINT_HIGH, "UPnP: DeletePortMapping failed: {}\n", r); is_upnp_ok = false; } else is_upnp_ok = true; } #endif // // UDPsocket // SOCKET UDPsocket (void) { SOCKET s; // allocate a socket s = socket (PF_INET, SOCK_DGRAM, IPPROTO_UDP); if (s == INVALID_SOCKET) I_FatalError ("can't create socket"); return s; } // // BindToLocalPort // void BindToLocalPort (SOCKET s, u_short wanted) { int v; struct sockaddr_in address; memset (&address, 0, sizeof(address)); address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; u_short next = wanted; // denis - try several ports do { address.sin_port = htons(next++); v = bind (s, (sockaddr *)&address, sizeof(address)); if(next > wanted + 32) { I_FatalError ("BindToPort: error"); return; } }while (v == SOCKET_ERROR); char tmp[32] = ""; snprintf(tmp, 32, "%d", next - 1); port.ForceSet(tmp); #ifdef ODA_HAVE_MINIUPNP std::string ip = NET_GetLocalAddress(); if (!ip.empty()) { sv_upnp_internalip.Set(ip.c_str()); PrintFmt(PRINT_HIGH, "UPnP: Internal IP address is: {}\n", ip); upnp_add_redir(ip.c_str(), next - 1); } else { PrintFmt(PRINT_HIGH, "UPnP: Could not get first internal IP address, " "UPnP will not function\n"); is_upnp_ok = false; } #endif PrintFmt(PRINT_HIGH, "Bound to local port {}\n", next - 1); } void CloseNetwork (void) { #ifdef ODA_HAVE_MINIUPNP upnp_rem_redir (port); #endif closesocket (inet_socket); #ifdef _WIN32 WSACleanup (); #endif } // this is from Quake source code :) void SockadrToNetadr (struct sockaddr_in *s, netadr_t *a) { memcpy(&(a->ip), &(s->sin_addr), sizeof(struct in_addr)); a->port = s->sin_port; } void NetadrToSockadr (netadr_t *a, struct sockaddr_in *s) { memset (s, 0, sizeof(*s)); s->sin_family = AF_INET; memcpy(&(s->sin_addr), &(a->ip), sizeof(struct in_addr)); s->sin_port = a->port; } char *NET_AdrToString (netadr_t a) { static char s[64]; snprintf (s, 64, "%i.%i.%i.%i:%i", a.ip[0], a.ip[1], a.ip[2], a.ip[3], ntohs(a.port)); return s; } bool NET_StringToAdr (const char *s, netadr_t *a) { struct hostent *h; struct sockaddr_in sadr; char *colon; char copy[256]; memset (&sadr, 0, sizeof(sadr)); sadr.sin_family = AF_INET; sadr.sin_port = 0; strncpy (copy, s, sizeof(copy) - 1); copy[sizeof(copy) - 1] = 0; // strip off a trailing :port if present for (colon = copy ; *colon ; colon++) if (*colon == ':') { *colon = 0; sadr.sin_port = htons(atoi(colon+1)); } if (! (h = gethostbyname(copy)) ) return 0; *(int *)&sadr.sin_addr = *(int *)h->h_addr_list[0]; SockadrToNetadr (&sadr, a); return true; } bool NET_CompareAdr (netadr_t a, netadr_t b) { if (a.ip[0] == b.ip[0] && a.ip[1] == b.ip[1] && a.ip[2] == b.ip[2] && a.ip[3] == b.ip[3] && a.port == b.port) return true; return false; } #ifdef _WIN32 typedef int socklen_t; #endif int NET_GetPacket (void) { int ret; struct sockaddr_in from; socklen_t fromlen; fromlen = sizeof(from); net_message.clear(); ret = recvfrom (inet_socket, (char *)net_message.ptr(), net_message.maxsize(), 0, (struct sockaddr *)&from, &fromlen); if (ret == -1) { #ifdef _WIN32 errno = WSAGetLastError(); if (errno == WSAEWOULDBLOCK) return false; if (errno == WSAECONNRESET) return false; if (errno == WSAEMSGSIZE) { PrintFmt(PRINT_HIGH, "Warning: Oversize packet from {}\n", NET_AdrToString (net_from)); return false; } PrintFmt(PRINT_HIGH, "NET_GetPacket: {}\n", strerror(errno)); return false; #else if (errno == EWOULDBLOCK) return false; if (errno == ECONNREFUSED) return false; PrintFmt(PRINT_HIGH, "NET_GetPacket: {}\n", strerror(errno)); return false; #endif } net_message.setcursize(ret); SockadrToNetadr (&from, &net_from); return ret; } int NET_SendPacket (buf_t &buf, netadr_t &to) { int ret; struct sockaddr_in addr; // [SL] 2011-07-06 - Don't try to send a packet if we're not really connected // (eg, a netdemo is being played back) if (simulated_connection) { buf.clear(); return 0; } NetadrToSockadr (&to, &addr); ret = sendto(inet_socket, (const char *)buf.ptr(), buf.size(), 0, (struct sockaddr *)&addr, sizeof(addr)); buf.clear(); if (ret == -1) { #ifdef _WIN32 int err = WSAGetLastError(); // wouldblock is silent if (err == WSAEWOULDBLOCK) return 0; #else if (errno == EWOULDBLOCK) return 0; if (errno == ECONNREFUSED) return 0; PrintFmt(PRINT_HIGH, "NET_SendPacket: {}\n", strerror(errno)); #endif } return ret; } #ifndef HOST_NAME_MAX #define HOST_NAME_MAX 256 #endif std::string NET_GetLocalAddress (void) { static char buff[HOST_NAME_MAX]; hostent *ent; struct in_addr addr; gethostname(buff, HOST_NAME_MAX); buff[HOST_NAME_MAX - 1] = 0; ent = gethostbyname(buff); // Return the first, IPv4 address if (ent && ent->h_addrtype == AF_INET && ent->h_addr_list[0] != NULL) { addr.s_addr = *(u_long *)ent->h_addr_list[0]; std::string ipstr = inet_ntoa(addr); PrintFmt(PRINT_HIGH, "Bound to IP: {}\n", ipstr); return ipstr; } else { PrintFmt(PRINT_HIGH, "Could not look up host IP address from hostname\n"); return ""; } } void SZ_Clear (buf_t *buf) { buf->clear(); } void SZ_Write (buf_t *b, const void *data, int length) { b->WriteChunk((const char *)data, length); } void SZ_Write (buf_t *b, const byte *data, int startpos, int length) { b->WriteChunk((const char *)data, length, startpos); } // // MSG_WriteMarker // // denis - use this function to mark the start of your server message // as it allows for better debugging and optimization of network code // // [ML] 8/4/10: Moved to sv_main and slightly modified to provide an adequate // but temporary fix for bug 594 until netcode_bringup2 is complete. // Thanks to spleen for providing good brainpower! // // [SL] 2011-07-17 - Moved back to i_net.cpp so that it can be used by // both client & server code. Client has a stub function for SV_SendPackets. // void SV_SendPackets(void); // // MSG_WriteMarker // // denis - use this function to mark the start of your client message // as it allows for better debugging and optimization of network code // void MSG_WriteMarker (buf_t *b, clc_t c) { if (simulated_connection) return; b->WriteByte((byte)c); } void MSG_WriteByte (buf_t *b, byte c) { if (simulated_connection) return; b->WriteByte((byte)c); } void MSG_WriteChunk (buf_t *b, const void *p, unsigned l) { if (simulated_connection) return; b->WriteChunk((const char *)p, l); } void MSG_WriteSVC(buf_t* b, const google::protobuf::Message& msg) { if (simulated_connection) return; static std::string buffer; if (!msg.SerializeToString(&buffer)) { PrintFmt( PRINT_WARNING, "WARNING: Could not serialize message \"{}\". This is most likely a bug.\n", msg.GetDescriptor()->full_name()); return; } // Do we actaully have room for this upcoming message? static constexpr size_t MAX_HEADER_SIZE = 4; // header + 3 bytes for varint size. if (b->cursize + MAX_HEADER_SIZE + msg.ByteSizeLong() >= MAX_UDP_SIZE) SV_SendPackets(); svc_t header = SVC_ResolveDescriptor(msg.GetDescriptor()); if (header == svc_noop) { PrintFmt(PRINT_WARNING, "WARNING: Could not find svc header for message \"{}\". This is most " "likely a bug.\n", msg.GetDescriptor()->full_name()); return; } #if 0 PrintFmt("{} ({})\n, {}\n", ::svc_info[header].getName(), msg.ByteSize(), msg.ShortDebugString()); #endif b->WriteByte(header); b->WriteUnVarint(buffer.size()); b->WriteChunk(buffer.data(), buffer.size()); } /** * @brief Broadcast message to all players. * * @param buf Type of buffer to broadcast in, per player. * @param msg Message to broadcast to all players. * @param skip If passed, skip this player id. */ void MSG_BroadcastSVC(const clientBuf_e buf, const google::protobuf::Message& msg, const int skipPlayer) { if (simulated_connection) return; static std::string buffer; if (!msg.SerializeToString(&buffer)) { PrintFmt( PRINT_WARNING, "WARNING: Could not serialize message \"{}\". This is most likely a bug.\n", msg.GetDescriptor()->full_name()); return; } svc_t header = SVC_ResolveDescriptor(msg.GetDescriptor()); if (header == svc_noop) { PrintFmt(PRINT_WARNING, "WARNING: Could not find svc header for message \"{}\". This is most " "likely a bug.\n", msg.GetDescriptor()->full_name()); return; } for (auto& player : players) { if (!player.ingame()) continue; if (static_cast(player.id) == skipPlayer) continue; // Select the correct buffer. buf_t* b = buf == CLBUF_RELIABLE ? &player.client.reliablebuf : &player.client.netbuf; // Do we actaully have room for this upcoming message? static constexpr size_t MAX_HEADER_SIZE = 4; // header + 3 bytes for varint size. if (b->cursize + MAX_HEADER_SIZE + msg.ByteSizeLong() >= MAX_UDP_SIZE) SV_SendPackets(); b->WriteByte(header); b->WriteUnVarint(buffer.size()); b->WriteChunk(buffer.data(), buffer.size()); } } void MSG_WriteShort (buf_t *b, short c) { if (simulated_connection) return; b->WriteShort(c); } void MSG_WriteLong (buf_t *b, int c) { if (simulated_connection) return; b->WriteLong(c); } void MSG_WriteUnVarint(buf_t* b, unsigned int uv) { if (simulated_connection) return; b->WriteUnVarint(uv); } void MSG_WriteVarint(buf_t* b, int v) { if (simulated_connection) return; b->WriteVarint(v); } // // MSG_WriteBool // // Write an boolean value to a buffer void MSG_WriteBool(buf_t *b, bool Boolean) { if (simulated_connection) return; MSG_WriteByte(b, Boolean ? 1 : 0); } // // MSG_WriteFloat // // Write a floating point number to a buffer void MSG_WriteFloat(buf_t *b, float Float) { if (simulated_connection) return; std::stringstream StringStream; StringStream << Float; MSG_WriteString(b, StringStream.str().c_str()); } // // MSG_WriteString // // Write a string to a buffer and null terminate it void MSG_WriteString (buf_t *b, const char *s) { if (simulated_connection) return; b->WriteString(s); } unsigned int toInt(char c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'A' && c <= 'F') return 10 + c - 'A'; if (c >= 'a' && c <= 'f') return 10 + c - 'a'; return -1; } // // MSG_WriteHexString // // Converts a hexidecimal string to its binary representation void MSG_WriteHexString(buf_t *b, const char *s) { byte output[255]; // Nothing to write? if (!(s && (*s))) { MSG_WriteByte(b, 0); return; } const size_t numdigits = strlen(s) / 2; if (numdigits > ARRAY_LENGTH(output)) { PrintFmt(PRINT_HIGH, "MSG_WriteHexString: too many digits\n"); return; } for (size_t i = 0; i < numdigits; ++i) { output[i] = (char)(16 * toInt(s[2*i]) + toInt(s[2*i+1])); } MSG_WriteByte(b, (byte)numdigits); MSG_WriteChunk(b, output, numdigits); } int MSG_BytesLeft(void) { return net_message.BytesLeftToRead(); } int MSG_ReadByte (void) { return net_message.ReadByte(); } int MSG_NextByte (void) { return net_message.NextByte(); } void *MSG_ReadChunk (const size_t &size) { return net_message.ReadChunk(size); } size_t MSG_SetOffset (const size_t &offset, const buf_t::seek_loc_t &loc) { return net_message.SetOffset(offset, loc); } // Output buffer size for LZO compression, extra space in case uncompressable #define OUT_LEN(a) ((a) + (a) / 16 + 64 + 3) // size above which packets get compressed (empirical), does not apply to adaptive compression #define MINILZO_COMPRESS_MINPACKETSIZE 0xFF // // MSG_DecompressMinilzo // bool MSG_DecompressMinilzo () { // decompress back onto the receive buffer size_t left = MSG_BytesLeft(); if(decompressed.maxsize() < net_message.maxsize()) decompressed.resize(net_message.maxsize()); lzo_uint newlen = net_message.maxsize(); unsigned int r = lzo1x_decompress_safe (net_message.ptr() + net_message.BytesRead(), left, decompressed.ptr(), &newlen, NULL); if(r != LZO_E_OK) { PrintFmt(PRINT_HIGH, "Error: minilzo packet decompression failed with error {:X}\n", r); return false; } net_message.clear(); memcpy(net_message.ptr(), decompressed.ptr(), newlen); net_message.cursize = newlen; return true; } // // MSG_CompressMinilzo // bool MSG_CompressMinilzo (buf_t &buf, size_t start_offset, size_t write_gap) { if(buf.size() < MINILZO_COMPRESS_MINPACKETSIZE) return false; lzo_uint outlen = OUT_LEN(buf.maxsize() - start_offset - write_gap); size_t total_len = outlen + start_offset + write_gap; if(compressed.maxsize() < total_len) compressed.resize(total_len); int r = lzo1x_1_compress (buf.ptr() + start_offset, buf.size() - start_offset, compressed.ptr() + start_offset + write_gap, &outlen, wrkmem); // worth the effort? if(r != LZO_E_OK || outlen >= (buf.size() - start_offset - write_gap)) return false; memcpy(compressed.ptr(), buf.ptr(), start_offset); SZ_Clear(&buf); MSG_WriteChunk(&buf, compressed.ptr(), outlen + start_offset + write_gap); return true; } int MSG_ReadShort (void) { return net_message.ReadShort(); } int MSG_ReadLong (void) { return net_message.ReadLong(); } unsigned int MSG_ReadUnVarint() { return net_message.ReadUnVarint(); } int MSG_ReadVarint() { return net_message.ReadVarint(); } // // MSG_ReadBool // // Read a boolean value bool MSG_ReadBool(void) { int Value = net_message.ReadByte(); if (Value < 0 || Value > 1) { DPrintFmt("MSG_ReadBool: Value is not 0 or 1, possibly corrupted packet"); return (Value ? true : false); } return (Value ? true : false); } // // MSG_ReadString // // Read a null terminated string const char *MSG_ReadString (void) { return net_message.ReadString(); } // // MSG_ReadFloat // // Read a floating point number float MSG_ReadFloat(void) { std::stringstream StringStream; float Float; StringStream << MSG_ReadString(); StringStream >> Float; return Float; } /** * @brief Initialize a svc_info member. * * @detail do-while is used to force a semicolon afterwards. */ #define SVC_INFO(n) \ do \ { \ ::svc_info[n].id = n; \ ::svc_info[n].msgName = #n; \ ::svc_info[n].msgFormat = "x"; \ } while (false) /** * @brief Initialize a clc_info member. * * @detail do-while is used to force a semicolon afterwards. */ #define CLC_INFO(n) \ do \ { \ ::clc_info[n].id = n; \ ::clc_info[n].msgName = #n; \ ::clc_info[n].msgFormat = "x"; \ } while (false) // // InitNetMessageFormats // static void InitNetMessageFormats() { // Server Messages. SVC_INFO(svc_noop); SVC_INFO(svc_disconnect); SVC_INFO(svc_playerinfo); SVC_INFO(svc_moveplayer); SVC_INFO(svc_updatelocalplayer); SVC_INFO(svc_levellocals); SVC_INFO(svc_pingrequest); SVC_INFO(svc_updateping); SVC_INFO(svc_spawnmobj); SVC_INFO(svc_disconnectclient); SVC_INFO(svc_loadmap); SVC_INFO(svc_consoleplayer); SVC_INFO(svc_explodemissile); SVC_INFO(svc_removemobj); SVC_INFO(svc_userinfo); SVC_INFO(svc_updatemobj); SVC_INFO(svc_spawnplayer); SVC_INFO(svc_damageplayer); SVC_INFO(svc_killmobj); SVC_INFO(svc_raisemobj); SVC_INFO(svc_fireweapon); SVC_INFO(svc_updatesector); SVC_INFO(svc_print); SVC_INFO(svc_playermembers); SVC_INFO(svc_teammembers); SVC_INFO(svc_activateline); SVC_INFO(svc_movingsector); SVC_INFO(svc_playsound); SVC_INFO(svc_reconnect); SVC_INFO(svc_exitlevel); SVC_INFO(svc_touchspecial); SVC_INFO(svc_forceteam); SVC_INFO(svc_switch); SVC_INFO(svc_say); SVC_INFO(svc_spawnhiddenplayer); SVC_INFO(svc_updatedeaths); SVC_INFO(svc_ctfrefresh); SVC_INFO(svc_ctfevent); SVC_INFO(svc_serversettings); SVC_INFO(svc_connectclient); SVC_INFO(svc_midprint); SVC_INFO(svc_servergametic); SVC_INFO(svc_inttimeleft); SVC_INFO(svc_fullupdatedone); SVC_INFO(svc_railtrail); SVC_INFO(svc_playerstate); SVC_INFO(svc_levelstate); SVC_INFO(svc_resetmap); SVC_INFO(svc_playerqueuepos); SVC_INFO(svc_fullupdatestart); SVC_INFO(svc_lineupdate); SVC_INFO(svc_sectorproperties); SVC_INFO(svc_linesideupdate); SVC_INFO(svc_mobjstate); SVC_INFO(svc_damagemobj); SVC_INFO(svc_executelinespecial); SVC_INFO(svc_executeacsspecial); SVC_INFO(svc_thinkerupdate); SVC_INFO(svc_netdemocap); SVC_INFO(svc_netdemostop); SVC_INFO(svc_netdemoloadsnap); SVC_INFO(svc_vote_update); SVC_INFO(svc_maplist); SVC_INFO(svc_maplist_update); SVC_INFO(svc_maplist_index); SVC_INFO(svc_toast); SVC_INFO(svc_hordeinfo); SVC_INFO(svc_max); // Client Messages. CLC_INFO(clc_abort); CLC_INFO(clc_reserved1); CLC_INFO(clc_disconnect); CLC_INFO(clc_say); CLC_INFO(clc_move); CLC_INFO(clc_userinfo); CLC_INFO(clc_pingreply); CLC_INFO(clc_rate); CLC_INFO(clc_ack); CLC_INFO(clc_rcon); CLC_INFO(clc_rcon_password); CLC_INFO(clc_changeteam); CLC_INFO(clc_ctfcommand); CLC_INFO(clc_spectate); CLC_INFO(clc_wantwad); CLC_INFO(clc_kill); CLC_INFO(clc_cheat); CLC_INFO(clc_callvote); CLC_INFO(clc_maplist); CLC_INFO(clc_maplist_update); CLC_INFO(clc_getplayerinfo); CLC_INFO(clc_netcmd); CLC_INFO(clc_spy); CLC_INFO(clc_privmsg); CLC_INFO(clc_max); } #undef SVC_INFO #undef CLC_INFO CVAR_FUNC_IMPL(net_rcvbuf) { int n = var.asInt(); if (setsockopt(inet_socket, SOL_SOCKET, SO_RCVBUF, SETSOCKOPTCAST(&n), (int) sizeof(n)) == -1) { PrintFmt(PRINT_HIGH, "setsockopt SO_RCVBUF: {}", strerror(errno)); } else { PrintFmt(PRINT_HIGH, "net_rcvbuf set to {}\n", n); } } CVAR_FUNC_IMPL(net_sndbuf) { int n = var.asInt(); if (setsockopt(inet_socket, SOL_SOCKET, SO_SNDBUF, SETSOCKOPTCAST(&n), (int) sizeof(n)) == -1) { PrintFmt(PRINT_HIGH, "setsockopt SO_SNDBUF: {}", strerror(errno)); } else { PrintFmt(PRINT_HIGH, "net_sndbuf set to {}\n", n); } } // // InitNetCommon // void InitNetCommon(void) { unsigned long _true = true; #ifdef _WIN32 WSADATA wsad; WSAStartup( MAKEWORD(2,2), &wsad ); #endif inet_socket = UDPsocket (); #ifdef ODA_HAVE_MINIUPNP init_upnp(); #endif BindToLocalPort (inet_socket, localport); if (ioctlsocket(inet_socket, FIONBIO, &_true) == -1) I_FatalError ("UDPsocket: ioctl FIONBIO: {}", strerror(errno)); // enter message information into message info structs InitNetMessageFormats(); SZ_Clear(&net_message); } // // NetWaitOrTimeout // // denis - yields CPU control briefly; shorter wait when data is available // bool NetWaitOrTimeout(size_t ms) { struct timeval timeout = {0, int(1000*ms) + 1}; fd_set fds; FD_ZERO(&fds); FD_SET(inet_socket, &fds); int ret = select(inet_socket + 1, &fds, NULL, NULL, &timeout); if(ret == 1) return true; #ifdef _WIN32 // handle SOCKET_ERROR if(ret == SOCKET_ERROR) PrintFmt(PRINT_HIGH, "select returned SOCKET_ERROR: {}\n", WSAGetLastError()); #else // handle -1 if(ret == -1 && ret != EINTR) PrintFmt(PRINT_HIGH, "select returned -1: {}\n", strerror(errno)); #endif return false; } void I_SetPort(netadr_t &addr, int port) { addr.port = htons(port); } VERSION_CONTROL (i_net_cpp, "$Id$")