// SONIC ROBO BLAST 2 //----------------------------------------------------------------------------- // Copyright (C) 1998-2000 by DooM Legacy Team. // Copyright (C) 1999-2018 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. // See the 'LICENSE' file for more details. //----------------------------------------------------------------------------- /// \file i_tcp.c /// \brief TCP driver, socket code. /// This is not really OS-dependent because all OSes have the same socket API. /// Just use ifdef for OS-dependent parts. #include #include #include #ifdef __GNUC__ #include #endif #ifdef __OS2__ #include #include #endif // __OS2__ #ifndef NO_IPV6 //#define HAVE_IPV6 #endif #ifdef _WIN32 #define USE_WINSOCK #endif //WIN32 OS #ifdef USE_WINSOCK #include #define addrinfo_t ADDRINFOA #else #define addrinfo_t struct addrinfo #endif #include "doomdef.h" #ifndef USE_WINSOCK #include #ifdef __APPLE_CC__ #ifndef _BSD_SOCKLEN_T_ #define _BSD_SOCKLEN_T_ #endif //_BSD_SOCKLEN_T_ #endif //__APPLE_CC__ #include #include #include #include #endif //normal BSD API #include #include #if defined (__unix__) || defined (__APPLE__) || defined (UNIXCOMMON) #include #endif // UNIXCOMMON #ifdef USE_WINSOCK // some undefined under win32 #undef errno //#define errno WSAGetLastError() //Alam_GBC: this is the correct way, right? #define errno h_errno // some very strange things happen when not using h_error?!? #ifdef EWOULDBLOCK #undef EWOULDBLOCK #endif #define EWOULDBLOCK WSAEWOULDBLOCK #ifdef EMSGSIZE #undef EMSGSIZE #endif #define EMSGSIZE WSAEMSGSIZE #ifdef ECONNREFUSED #undef ECONNREFUSED #endif #define ECONNREFUSED WSAECONNREFUSED #ifdef ETIMEDOUT #undef ETIMEDOUT #endif #define ETIMEDOUT WSAETIMEDOUT #ifdef EHOSTUNREACH #undef EHOSTUNREACH #endif #define EHOSTUNREACH WSAEHOSTUNREACH #ifdef ENETUNREACH #undef ENETUNREACH #endif #define ENETUNREACH WSAENETUNREACH #ifndef IOC_VENDOR #define IOC_VENDOR 0x18000000 #endif #ifndef _WSAIOW #define _WSAIOW(x,y) (IOC_IN|(x)|(y)) #endif #ifndef SIO_UDP_CONNRESET #define SIO_UDP_CONNRESET _WSAIOW(IOC_VENDOR,12) #endif #ifndef AI_ADDRCONFIG #define AI_ADDRCONFIG 0x00000400 #endif #ifndef STATUS_INVALID_PARAMETER #define STATUS_INVALID_PARAMETER 0xC000000D #endif #endif // USE_WINSOCK typedef union { struct sockaddr any; struct sockaddr_in ip4; #ifdef HAVE_IPV6 struct sockaddr_in6 ip6; #endif } mysockaddr_t; #ifdef HAVE_MINIUPNPC #ifdef STATIC_MINIUPNPC #define STATICLIB #endif #include "miniupnpc/miniwget.h" #include "miniupnpc/miniupnpc.h" #include "miniupnpc/upnpcommands.h" #undef STATICLIB static UINT8 UPNP_support = TRUE; #endif #include "i_system.h" #include "i_time.h" #include "i_net.h" #include "d_net.h" #include "d_netfil.h" #include "i_tcp.h" #include "m_argv.h" #include "stun.h" #include "d_main.h" #include "z_zone.h" #include "doomstat.h" // win32 #ifdef USE_WINSOCK // winsock stuff (in winsock a socket is not a file) #define ioctl ioctlsocket #define close closesocket #endif #define DEFAULTPORT "5029" #ifdef USE_WINSOCK typedef SOCKET SOCKET_TYPE; #define ERRSOCKET (SOCKET_ERROR) #else #if defined (__unix__) || defined (__APPLE__) || defined (__HAIKU__) typedef int SOCKET_TYPE; #else typedef unsigned long SOCKET_TYPE; #endif #define ERRSOCKET (-1) #endif #define IPV6_MULTICAST_ADDRESS "ff15::57e1:1a12" typedef struct { mysockaddr_t address; UINT8 mask; char *username; char *reason; time_t timestamp; } banned_t; static SOCKET_TYPE mysockets[MAXNETNODES+1] = {ERRSOCKET}; static size_t mysocketses = 0; static int myfamily[MAXNETNODES+1] = {0}; static SOCKET_TYPE nodesocket[MAXNETNODES+1] = {ERRSOCKET}; static mysockaddr_t clientaddress[MAXNETNODES+1]; static mysockaddr_t broadcastaddress[MAXNETNODES+1]; static size_t broadcastaddresses = 0; static boolean nodeconnected[MAXNETNODES+1]; static banned_t *banned; /* See ../doc/Holepunch-Protocol.txt */ #ifdef HOLEPUNCH static const INT32 hole_punch_magic = MSBF_LONG (0x52eb11); #endif static size_t numbans = 0; static size_t banned_size = 0; static bannednode_t SOCK_bannednode[MAXNETNODES+1]; /// \note do we really need the +1? static boolean init_tcp_driver = false; static const char *serverport_name = DEFAULTPORT; static const char *clientport_name;/* any port */ #ifdef USE_WINSOCK // stupid microsoft makes things complicated static char *get_WSAErrorStr(int e) { static char buf[256]; // allow up to 255 bytes buf[0] = '\0'; FormatMessageA( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, (DWORD)e, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)buf, sizeof (buf), NULL); if (!buf[0]) // provide a fallback error message if no message is available for some reason sprintf(buf, "Unknown error"); return buf; } #undef strerror #define strerror get_WSAErrorStr #endif #ifdef USE_WINSOCK #define inet_ntop inet_ntopA static const char* inet_ntopA(short af, const void *cp, char *buf, socklen_t len) { DWORD Dlen = len, AFlen = 0; SOCKADDR_STORAGE any; LPSOCKADDR anyp = (LPSOCKADDR)&any; LPSOCKADDR_IN any4 = (LPSOCKADDR_IN)&any; LPSOCKADDR_IN6 any6 = (LPSOCKADDR_IN6)&any; if (!buf) { WSASetLastError(STATUS_INVALID_PARAMETER); return NULL; } if (af != AF_INET && af != AF_INET6) { WSASetLastError(WSAEAFNOSUPPORT); return NULL; } ZeroMemory(&any, sizeof(SOCKADDR_STORAGE)); any.ss_family = af; switch (af) { case AF_INET: { CopyMemory(&any4->sin_addr, cp, sizeof(IN_ADDR)); AFlen = sizeof(SOCKADDR_IN); break; } case AF_INET6: { CopyMemory(&any6->sin6_addr, cp, sizeof(IN6_ADDR)); AFlen = sizeof(SOCKADDR_IN6); break; } } if (WSAAddressToStringA(anyp, AFlen, NULL, buf, &Dlen) == SOCKET_ERROR) return NULL; return buf; } #endif #ifdef HAVE_MINIUPNPC // based on old XChat patch static struct UPNPUrls urls; static struct IGDdatas data; static char lanaddr[64]; static void I_ShutdownUPnP(void) { FreeUPNPUrls(&urls); } static inline void I_InitUPnP(void) { struct UPNPDev * devlist = NULL; int upnp_error = -2; CONS_Printf(M_GetText("Looking for UPnP Internet Gateway Device\n")); devlist = upnpDiscover(2000, NULL, NULL, 0, false, &upnp_error); if (devlist) { struct UPNPDev *dev = devlist; char * descXML; int descXMLsize = 0; while (dev) { if (strstr (dev->st, "InternetGatewayDevice")) break; dev = dev->pNext; } if (!dev) dev = devlist; /* defaulting to first device */ CONS_Printf(M_GetText("Found UPnP device:\n desc: %s\n st: %s\n"), dev->descURL, dev->st); UPNP_GetValidIGD(devlist, &urls, &data, lanaddr, sizeof(lanaddr)); CONS_Printf(M_GetText("Local LAN IP address: %s\n"), lanaddr); descXML = miniwget(dev->descURL, &descXMLsize); if (descXML) { parserootdesc(descXML, descXMLsize, &data); free(descXML); descXML = NULL; memset(&urls, 0, sizeof(struct UPNPUrls)); memset(&data, 0, sizeof(struct IGDdatas)); GetUPNPUrls(&urls, &data, dev->descURL); I_AddExitFunc(I_ShutdownUPnP); } freeUPNPDevlist(devlist); } else if (upnp_error == UPNPDISCOVER_SOCKET_ERROR) { CONS_Printf(M_GetText("No UPnP devices discovered\n")); } } static inline void I_UPnP_add(const char * addr, const char *port, const char * servicetype) { if (addr == NULL) addr = lanaddr; if (!urls.controlURL || urls.controlURL[0] == '\0') return; UPNP_AddPortMapping(urls.controlURL, data.first.servicetype, port, port, addr, "SRB2", servicetype, NULL, NULL); } static inline void I_UPnP_rem(const char *port, const char * servicetype) { if (!urls.controlURL || urls.controlURL[0] == '\0') return; UPNP_DeletePortMapping(urls.controlURL, data.first.servicetype, port, servicetype, NULL); } #endif static const char *SOCK_AddrToStr(mysockaddr_t *sk) { static char s[64]; // 255.255.255.255:65535 or // [ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]:65535 #ifdef HAVE_IPV6 int v6 = (sk->any.sa_family == AF_INET6); #else int v6 = 0; #endif void *addr; int e = 0; // save error code so it can't be modified later code and avoid calling WSAGetLastError() more then once if (sk->any.sa_family == AF_INET) addr = &sk->ip4.sin_addr; #ifdef HAVE_IPV6 else if(sk->any.sa_family == AF_INET6) addr = &sk->ip6.sin6_addr; #endif else addr = NULL; if (addr == NULL) { sprintf(s, "No address"); } else if (inet_ntop(sk->any.sa_family, addr, &s[v6], sizeof (s) - v6) == NULL) { e = errno; sprintf(s, "Unknown family type, error #%u: %s", e, strerror(e)); } #ifdef HAVE_IPV6 else if(sk->any.sa_family == AF_INET6) { s[0] = '['; strcat(s, "]"); if (sk->ip6.sin6_port != 0) strcat(s, va(":%d", ntohs(sk->ip6.sin6_port))); } #endif else if (sk->any.sa_family == AF_INET && sk->ip4.sin_port != 0) strcat(s, va(":%d", ntohs(sk->ip4.sin_port))); return s; } static const char *SOCK_GetNodeAddress(INT32 node) { if (node == 0) return "self"; if (!nodeconnected[node]) return NULL; return SOCK_AddrToStr(&clientaddress[node]); } static const char *SOCK_GetBanAddress(size_t ban) { if (ban >= numbans) return NULL; return SOCK_AddrToStr(&banned[ban].address); } static const char *SOCK_GetBanMask(size_t ban) { static char s[16]; //255.255.255.255 netmask? no, just CDIR for only if (ban >= numbans) return NULL; if (sprintf(s,"%d",banned[ban].mask) > 0) return s; return NULL; } static const char *SOCK_GetBanUsername(size_t ban) { if (ban >= numbans) return NULL; return banned[ban].username; } static const char *SOCK_GetBanReason(size_t ban) { if (ban >= numbans) return NULL; return banned[ban].reason; } static time_t SOCK_GetUnbanTime(size_t ban) { if (ban >= numbans) return NO_BAN_TIME; return banned[ban].timestamp; } #ifdef HAVE_IPV6 static boolean SOCK_cmpipv6(mysockaddr_t *a, mysockaddr_t *b, UINT8 mask) { UINT8 bitmask; I_Assert(mask <= 128); if (mask == 0) mask = 128; if (memcmp(&a->ip6.sin6_addr.s6_addr, &b->ip6.sin6_addr.s6_addr, mask / 8) != 0) return false; if (mask % 8 == 0) return true; bitmask = 255 << (mask % 8); return (a->ip6.sin6_addr.s6_addr[mask / 8] & bitmask) == (b->ip6.sin6_addr.s6_addr[mask / 8] & bitmask); } #endif static boolean SOCK_cmpaddr(mysockaddr_t *a, mysockaddr_t *b, UINT8 mask) { UINT32 bitmask = INADDR_NONE; if (a->any.sa_family != b->any.sa_family) return false; if (mask && mask < 32) bitmask = htonl((UINT32)(-1) << (32 - mask)); if (b->any.sa_family == AF_INET) return (a->ip4.sin_addr.s_addr & bitmask) == (b->ip4.sin_addr.s_addr & bitmask) && (b->ip4.sin_port == 0 || (a->ip4.sin_port == b->ip4.sin_port)); #ifdef HAVE_IPV6 else if (b->any.sa_family == AF_INET6) return SOCK_cmpipv6(a, b, mask) && (b->ip6.sin6_port == 0 || (a->ip6.sin6_port == b->ip6.sin6_port)); #endif else return false; } // This is a hack. For some reason, nodes aren't being freed properly. // This goes through and cleans up what nodes were supposed to be freed. /** \warning This function causes the file downloading to stop if someone joins. * How? Because it removes nodes that are connected but not in game, * which is exactly what clients downloading a file are. */ static void cleanupnodes(void) { SINT8 j; if (!Playing()) return; // Why can't I start at zero? for (j = 1; j < MAXNETNODES; j++) { if (!(nodeingame[j] || SV_SendingFile(j))) nodeconnected[j] = false; } } static SINT8 getfreenode(void) { SINT8 j; cleanupnodes(); for (j = 0; j < MAXNETNODES; j++) { if (!nodeconnected[j]) { nodeconnected[j] = true; return j; } } /** \warning No free node? Just in case a node might not have been freed properly, * look if there are connected nodes that aren't in game, and forget them. * It's dirty, and might result in a poor guy having to restart * downloading a needed wad, but it's better than not letting anyone join... */ /*I_Error("No more free nodes!!1!11!11!!1111\n"); for (j = 1; j < MAXNETNODES; j++) if (!nodeingame[j]) return j;*/ return -1; } #ifdef _DEBUG void Command_Numnodes(void) { INT32 connected = 0; INT32 ingame = 0; INT32 i; for (i = 1; i < MAXNETNODES; i++) { if (!(nodeconnected[i] || nodeingame[i])) continue; if (nodeconnected[i]) connected++; if (nodeingame[i]) ingame++; CONS_Printf("%2d - ", i); if (nodetoplayer[i] != -1) CONS_Printf("player %.2d", nodetoplayer[i]); else CONS_Printf(" "); if (nodeconnected[i]) CONS_Printf(" - connected"); else CONS_Printf(" - "); if (nodeingame[i]) CONS_Printf(" - ingame"); else CONS_Printf(" - "); CONS_Printf(" - %s\n", I_GetNodeAddress(i)); } CONS_Printf("\n" "Connected: %d\n" "Ingame: %d\n", connected, ingame); } #endif #ifdef HOLEPUNCH /* not one of the reserved "local" addresses */ static boolean is_external_address(UINT32 p) { UINT8 a = (p & 255); UINT8 b = ((p >> 8) & 255); if (p == (UINT32)~0) /* 255.255.255.255 */ return 0; switch (a) { case 0: case 10: case 127: return false; case 172: return (b & ~15) != 16; /* 16 - 31 */ case 192: return b != 168; default: return true; } } static boolean hole_punch(ssize_t c) { holepunch_t *holepunchpacket = HOLEPUNCH_DATA(doomcom); /* See ../doc/Holepunch-Protocol.txt */ if (cv_rendezvousserver.string[0] && c == 10 && holepunchpacket->magic == hole_punch_magic && is_external_address(ntohl(holepunchpacket->addr))) { mysockaddr_t addr; addr.ip4.sin_family = AF_INET; addr.ip4.sin_addr.s_addr = holepunchpacket->addr; addr.ip4.sin_port = holepunchpacket->port; sendto(mysockets[0], NULL, 0, 0, &addr.any, sizeof addr.ip4); CONS_Debug(DBG_NETPLAY, "hole punching request from %s\n", SOCK_AddrToStr(&addr)); return true; } else { return false; } } #endif //ifdef HOLEPUNCH // Returns true if a packet was received from a new node, false in all other cases static boolean SOCK_Get(void) { size_t i, n; int j; ssize_t c; mysockaddr_t fromaddress; socklen_t fromlen; for (n = 0; n < mysocketses; n++) { fromlen = (socklen_t)sizeof(fromaddress); c = recvfrom(mysockets[n], (char *)&doomcom->data, MAXPACKETLENGTH, 0, (void *)&fromaddress, &fromlen); if (c > 0) { #ifdef USE_STUN if (STUN_got_response(doomcom->data, c)) { break; } #endif #ifdef HOLEPUNCH if (hole_punch(c)) { break; } #endif // find remote node number for (j = 1; j <= MAXNETNODES; j++) // include LAN { if (SOCK_cmpaddr(&fromaddress, &clientaddress[j], 0)) { doomcom->remotenode = (INT16)j; // good packet from a game player doomcom->datalength = (INT16)c; nodesocket[j] = mysockets[n]; return true; } } // not found // find a free slot j = getfreenode(); if (j > 0) { const time_t curTime = time(NULL); memcpy(&clientaddress[j], &fromaddress, fromlen); nodesocket[j] = mysockets[n]; DEBFILE(va("New node detected: node:%d address:%s\n", j, SOCK_GetNodeAddress(j))); doomcom->remotenode = (INT16)j; // good packet from a game player doomcom->datalength = (INT16)c; // check if it's a banned dude so we can send a refusal later for (i = 0; i < numbans; i++) { if (SOCK_cmpaddr(&fromaddress, &banned[i].address, banned[i].mask)) { if (banned[i].timestamp != NO_BAN_TIME) { if (curTime >= banned[i].timestamp) { SOCK_bannednode[j].timeleft = NO_BAN_TIME; SOCK_bannednode[j].banid = SIZE_MAX; DEBFILE("This dude was banned, but enough time has passed\n"); break; } SOCK_bannednode[j].timeleft = banned[i].timestamp - curTime; SOCK_bannednode[j].banid = i; DEBFILE("This dude has been temporarily banned\n"); break; } else { SOCK_bannednode[j].timeleft = NO_BAN_TIME; SOCK_bannednode[j].banid = i; DEBFILE("This dude has been banned\n"); break; } } } if (i == numbans) { SOCK_bannednode[j].timeleft = NO_BAN_TIME; SOCK_bannednode[j].banid = SIZE_MAX; } return true; } else DEBFILE("New node detected: No more free slots\n"); } } doomcom->remotenode = -1; // no packet return false; } static inline ssize_t SOCK_SendToAddr(SOCKET_TYPE socket, mysockaddr_t *sockaddr) { socklen_t d4 = (socklen_t)sizeof(struct sockaddr_in); #ifdef HAVE_IPV6 socklen_t d6 = (socklen_t)sizeof(struct sockaddr_in6); #endif socklen_t d, da = (socklen_t)sizeof(mysockaddr_t); switch (sockaddr->any.sa_family) { case AF_INET: d = d4; break; #ifdef HAVE_IPV6 case AF_INET6: d = d6; break; #endif default: d = da; break; } return sendto(socket, (char *)&doomcom->data, doomcom->datalength, 0, &sockaddr->any, d); } #define ALLOWEDERROR(x) ((x) == ECONNREFUSED || (x) == EWOULDBLOCK || (x) == EHOSTUNREACH || (x) == ENETUNREACH || (x) == EADDRNOTAVAIL) static void SOCK_Send(void) { ssize_t c = ERRSOCKET; int e = 0; // save error code so it can't be modified later code and avoid calling WSAGetLastError() more then once size_t i, j; if (doomcom->remotenode < 0 || !nodeconnected[doomcom->remotenode]) return; if (doomcom->remotenode == BROADCASTADDR) { for (i = 0; i < mysocketses; i++) { for (j = 0; j < broadcastaddresses; j++) { if (myfamily[i] == broadcastaddress[j].any.sa_family) { c = SOCK_SendToAddr(mysockets[i], &broadcastaddress[j]); if (c == ERRSOCKET) { e = errno; if (!ALLOWEDERROR(e)) break; } } } } } else if (nodesocket[doomcom->remotenode] == (SOCKET_TYPE)ERRSOCKET) { for (i = 0; i < mysocketses; i++) { if (myfamily[i] == clientaddress[doomcom->remotenode].any.sa_family) { c = SOCK_SendToAddr(mysockets[i], &clientaddress[doomcom->remotenode]); if (c == ERRSOCKET) { e = errno; if (!ALLOWEDERROR(e)) break; } } } } else { c = SOCK_SendToAddr(nodesocket[doomcom->remotenode], &clientaddress[doomcom->remotenode]); if (c == ERRSOCKET) { e = errno; } } if (c == ERRSOCKET && e != 0) // 0 means no socket for the address family was found { if (!ALLOWEDERROR(e)) I_Error("SOCK_Send, error sending to node %d (%s) #%u, %s", doomcom->remotenode, SOCK_GetNodeAddress(doomcom->remotenode), e, strerror(e)); } } #undef ALLOWEDERROR static void SOCK_FreeNodenum(INT32 numnode) { // can't disconnect from self :) if (numnode <= 0 || numnode > MAXNETNODES) return; DEBFILE(va("Free node %d (%s)\n", numnode, SOCK_GetNodeAddress(numnode))); nodeconnected[numnode] = false; nodesocket[numnode] = ERRSOCKET; // put invalid address memset(&clientaddress[numnode], 0, sizeof (clientaddress[numnode])); } // // UDPsocket // // allocate a socket static SOCKET_TYPE UDP_Bind(int family, struct sockaddr *addr, socklen_t addrlen) { SOCKET_TYPE s = socket(family, SOCK_DGRAM, IPPROTO_UDP); int opt; int rc; int e = 0; // save error code so it can't be modified later code and avoid calling WSAGetLastError() more then once socklen_t opts; #ifdef FIONBIO unsigned long trueval = true; #endif mysockaddr_t straddr; socklen_t len = sizeof(straddr); if (s == (SOCKET_TYPE)ERRSOCKET) return (SOCKET_TYPE)ERRSOCKET; #ifdef USE_WINSOCK { // Alam_GBC: disable the new UDP connection reset behavior for Win2k and up DWORD dwBytesReturned = 0; BOOL bfalse = FALSE; rc = WSAIoctl(s, SIO_UDP_CONNRESET, &bfalse, sizeof(bfalse), NULL, 0, &dwBytesReturned, NULL, NULL); if (rc == -1) { e = errno; I_OutputMsg("SIO_UDP_CONNRESET failed: #%u, %s\n", e, strerror(e)); } } #endif memcpy(&straddr, addr, addrlen); I_OutputMsg("Binding to %s\n", SOCK_AddrToStr(&straddr)); if (family == AF_INET) { if (straddr.ip4.sin_addr.s_addr == htonl(INADDR_ANY)) { opt = true; opts = (socklen_t)sizeof(opt); rc = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, opts); if (rc <= -1) { e = errno; I_OutputMsg("setting SO_REUSEADDR failed: #%u, %s\n", e, strerror(e)); } } // make it broadcastable opt = true; opts = (socklen_t)sizeof(opt); rc = setsockopt(s, SOL_SOCKET, SO_BROADCAST, (char *)&opt, opts); if (rc <= -1) { e = errno; CONS_Alert(CONS_WARNING, M_GetText("Could not get broadcast rights\n")); // I do not care anymore I_OutputMsg("setting SO_BROADCAST failed: #%u, %s\n", e, strerror(e)); } } #ifdef HAVE_IPV6 else if (family == AF_INET6) { if (memcmp(&straddr.ip6.sin6_addr, &in6addr_any, sizeof(in6addr_any)) == 0) //IN6_ARE_ADDR_EQUAL { opt = true; opts = (socklen_t)sizeof(opt); rc = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, opts); if (rc <= -1) { e = errno; I_OutputMsg("setting SO_REUSEADDR failed: #%u, %s\n", e, strerror(e)); } } #ifdef IPV6_V6ONLY // make it IPv6 ony opt = true; opts = (socklen_t)sizeof(opt); rc = setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&opt, opts); if (rc <= -1) { e = errno; CONS_Alert(CONS_WARNING, M_GetText("Could not limit IPv6 bind\n")); // I do not care anymore I_OutputMsg("setting IPV6_V6ONLY failed: #%u, %s\n", e, strerror(e)); } #endif } #endif rc = bind(s, addr, addrlen); if (rc == ERRSOCKET) { e = errno; close(s); I_OutputMsg("Binding failed: #%u, %s\n", e, strerror(e)); return (SOCKET_TYPE)ERRSOCKET; } #ifdef HAVE_IPV6 if (family == AF_INET6) { // we need to set all of this *after* binding to an address! if (memcmp(&straddr.ip6.sin6_addr, &in6addr_any, sizeof(in6addr_any)) == 0) //IN6_ARE_ADDR_EQUAL { struct ipv6_mreq maddr; inet_pton(AF_INET6, IPV6_MULTICAST_ADDRESS, &maddr.ipv6mr_multiaddr); maddr.ipv6mr_interface = 0; rc = setsockopt(s, IPPROTO_IPV6, IPV6_JOIN_GROUP, (const char *)&maddr, sizeof(maddr)); if (rc <= -1) { e = errno; CONS_Alert(CONS_WARNING, M_GetText("Could not register multicast address\n")); if (e == ENODEV) { close(s); I_OutputMsg("Binding failed: no IPv6 device\n"); return (SOCKET_TYPE)ERRSOCKET; } I_OutputMsg("setting IPV6_JOIN_GROUP failed: #%u, %s \n", e, strerror(e)); } } } #endif #ifdef FIONBIO // make it non blocking rc = ioctl(s, FIONBIO, &trueval); if (rc == -1) { e = errno; close(s); I_OutputMsg("FIOBIO failed: #%u, %s\n", e, strerror(e)); return (SOCKET_TYPE)ERRSOCKET; } #endif opt = 0; opts = (socklen_t)sizeof(opt); rc = getsockopt(s, SOL_SOCKET, SO_RCVBUF, (char *)&opt, &opts); if (rc <= -1) { e = errno; I_OutputMsg("getting SO_RCVBUF failed: #%u, %s\n", e, strerror(e)); } CONS_Printf(M_GetText("Network receive buffer: %dKb\n"), opt>>10); if (opt < 64<<10) // 64k { opt = 64<<10; opts = (socklen_t)sizeof(opt); rc = setsockopt(s, SOL_SOCKET, SO_RCVBUF, (char *)&opt, opts); if (rc <= -1) { e = errno; I_OutputMsg("setting SO_RCVBUF failed: #%u, %s\n", e, strerror(e)); } opt = 0; rc = getsockopt(s, SOL_SOCKET, SO_RCVBUF, (char *)&opt, &opts); if (rc <= -1) { e = errno; I_OutputMsg("getting SO_RCVBUF failed: #%u, %s\n", e, strerror(e)); } if (opt < 64<<10) CONS_Alert(CONS_WARNING, M_GetText("Can't set receive buffer length to 64k, file transfer will be bad\n")); else CONS_Printf(M_GetText("Network receive buffer set to: %dKb\n"), opt>>10); } opt = 0; opts = (socklen_t)sizeof(opt); rc = getsockopt(s, SOL_SOCKET, SO_SNDBUF, (char *)&opt, &opts); if (rc <= -1) { e = errno; I_OutputMsg("getting SO_SNDBUF failed: #%u, %s\n", e, strerror(e)); } CONS_Printf(M_GetText("Network send buffer: %dKb\n"), opt>>10); if (opt < 64<<10) // 64k { opt = 64<<10; opts = (socklen_t)sizeof(opt); rc = setsockopt(s, SOL_SOCKET, SO_SNDBUF, (char *)&opt, opts); if (rc <= -1) { e = errno; I_OutputMsg("setting SO_SNDBUF to %dKb failed: #%u, %s\n", opt>>10, e, strerror(e)); } opt = 0; rc = getsockopt(s, SOL_SOCKET, SO_SNDBUF, (char *)&opt, &opts); if (rc <= -1) { e = errno; I_OutputMsg("getting SO_SNDBUF failed: #%u, %s\n", e, strerror(e)); } if (opt < 64<<10) CONS_Alert(CONS_WARNING, M_GetText("Can't set send buffer length to 64k, file transfer will be bad\n")); else CONS_Printf(M_GetText("Network send system buffer set to: %dKb\n"), opt>>10); } rc = getsockname(s, &straddr.any, &len); if (rc != 0) { e = errno; CONS_Alert(CONS_WARNING, M_GetText("Failed to get port number\n")); I_OutputMsg("getsockname failed: #%u, %s\n", e, strerror(e)); } else { if (family == AF_INET) current_port = (UINT16)ntohs(straddr.ip4.sin_port); #ifdef HAVE_IPV6 else if (family == AF_INET6) current_port = (UINT16)ntohs(straddr.ip6.sin6_port); #endif } return s; } static boolean UDP_Socket(void) { size_t s; addrinfo_t *ai, *runp, hints; int gaie; #ifdef HAVE_IPV6 const INT32 b_ipv6 = M_CheckParm("-ipv6"); #endif const char *serv; for (s = 0; s < mysocketses; s++) mysockets[s] = ERRSOCKET; for (s = 0; s < MAXNETNODES+1; s++) nodesocket[s] = ERRSOCKET; s = 0; memset(&hints, 0x00, sizeof (hints)); hints.ai_flags = AI_NUMERICHOST; hints.ai_family = AF_INET; hints.ai_socktype = SOCK_DGRAM; hints.ai_protocol = IPPROTO_UDP; if (serverrunning) serv = serverport_name; else serv = clientport_name; if (M_CheckParm("-bindaddr")) { while (M_IsNextParm()) { gaie = getaddrinfo(M_GetNextParm(), serv, &hints, &ai); if (gaie == 0) { runp = ai; while (runp != NULL && s < MAXNETNODES+1) { mysockets[s] = UDP_Bind(runp->ai_family, runp->ai_addr, (socklen_t)runp->ai_addrlen); if (mysockets[s] != (SOCKET_TYPE)ERRSOCKET) { myfamily[s] = hints.ai_family; s++; } runp = runp->ai_next; } freeaddrinfo(ai); } } } else { gaie = getaddrinfo("0.0.0.0", serv, &hints, &ai); if (gaie == 0) { runp = ai; while (runp != NULL && s < MAXNETNODES+1) { mysockets[s] = UDP_Bind(runp->ai_family, runp->ai_addr, (socklen_t)runp->ai_addrlen); if (mysockets[s] != (SOCKET_TYPE)ERRSOCKET) { myfamily[s] = hints.ai_family; s++; #ifdef HAVE_MINIUPNPC if (UPNP_support) { I_UPnP_rem(serverport_name, "UDP"); I_UPnP_add(NULL, serverport_name, "UDP"); } #endif } runp = runp->ai_next; } freeaddrinfo(ai); } } #ifdef HAVE_IPV6 if (b_ipv6) { hints.ai_family = AF_INET6; if (M_CheckParm("-bindaddr6")) { while (M_IsNextParm()) { gaie = getaddrinfo(M_GetNextParm(), serv, &hints, &ai); if (gaie == 0) { runp = ai; while (runp != NULL && s < MAXNETNODES+1) { mysockets[s] = UDP_Bind(runp->ai_family, runp->ai_addr, (socklen_t)runp->ai_addrlen); if (mysockets[s] != (SOCKET_TYPE)ERRSOCKET) { myfamily[s] = hints.ai_family; s++; } runp = runp->ai_next; } freeaddrinfo(ai); } } } else { gaie = getaddrinfo("::", serv, &hints, &ai); if (gaie == 0) { runp = ai; while (runp != NULL && s < MAXNETNODES+1) { mysockets[s] = UDP_Bind(runp->ai_family, runp->ai_addr, (socklen_t)runp->ai_addrlen); if (mysockets[s] != (SOCKET_TYPE)ERRSOCKET) { myfamily[s] = hints.ai_family; s++; } runp = runp->ai_next; } freeaddrinfo(ai); } } } #endif mysocketses = s; if (s == 0) // no sockets? return false; s = 0; // ip + udp packetheaderlength = 20 + 8; // for stats clientaddress[s].any.sa_family = AF_INET; clientaddress[s].ip4.sin_port = htons(0); clientaddress[s].ip4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); //GetLocalAddress(); // my own ip s++; s = 0; // setup broadcast adress to BROADCASTADDR entry broadcastaddress[s].any.sa_family = AF_INET; broadcastaddress[s].ip4.sin_port = htons(atoi(DEFAULTPORT)); broadcastaddress[s].ip4.sin_addr.s_addr = htonl(INADDR_BROADCAST); s++; #ifdef HAVE_IPV6 if (b_ipv6) { broadcastaddress[s].any.sa_family = AF_INET6; broadcastaddress[s].ip6.sin6_port = htons(atoi(DEFAULTPORT)); broadcastaddress[s].ip6.sin6_flowinfo = 0; inet_pton(AF_INET6, IPV6_MULTICAST_ADDRESS, &broadcastaddress[s].ip6.sin6_addr); broadcastaddress[s].ip6.sin6_scope_id = 0; s++; } #endif broadcastaddresses = s; doomcom->extratics = 1; // internet is very high ping return true; } boolean I_InitTcpDriver(void) { boolean tcp_was_up = init_tcp_driver; if (!init_tcp_driver) { #ifdef USE_WINSOCK const WORD VerNeed = MAKEWORD(2,2); WSADATA WSAData; const int WSAresult = WSAStartup(VerNeed, &WSAData); if (WSAresult != 0) { LPCSTR WSError = NULL; switch (WSAresult) { case WSASYSNOTREADY: WSError = "The underlying network subsystem is not ready for network communication"; break; case WSAEINPROGRESS: WSError = "A blocking Windows Sockets 1.1 operation is in progress"; break; case WSAEPROCLIM: WSError = "Limit on the number of tasks supported by the Windows Sockets implementation has been reached"; break; case WSAEFAULT: WSError = "WSAData is not a valid pointer? What kind of setup do you have?"; break; default: WSError = va("Error code %u",WSAresult); break; } if (WSAresult != WSAVERNOTSUPPORTED) CONS_Debug(DBG_NETPLAY, "WinSock(TCP/IP) error: %s\n",WSError); } if (LOBYTE(WSAData.wVersion) != 2 || HIBYTE(WSAData.wVersion) != 2) { WSACleanup(); CONS_Debug(DBG_NETPLAY, "No WinSock(TCP/IP) 2.2 driver detected\n"); } CONS_Debug(DBG_NETPLAY, "WinSock description: %s\n",WSAData.szDescription); CONS_Debug(DBG_NETPLAY, "WinSock System Status: %s\n",WSAData.szSystemStatus); #endif init_tcp_driver = true; } if (!tcp_was_up && init_tcp_driver) { I_AddExitFunc(I_ShutdownTcpDriver); #ifdef HAVE_MINIUPNPC if (M_CheckParm("-useUPnP")) I_InitUPnP(); else UPNP_support = false; #endif } return init_tcp_driver; } static void SOCK_CloseSocket(void) { size_t i; for (i = 0; i < mysocketses; i++) { if (mysockets[i] != (SOCKET_TYPE)ERRSOCKET) close(mysockets[i]); mysockets[i] = ERRSOCKET; } mysocketses = 0; } void I_ShutdownTcpDriver(void) { SOCK_CloseSocket(); CONS_Printf("I_ShutdownTcpDriver: "); #ifdef USE_WINSOCK WSACleanup(); #endif CONS_Printf("shut down\n"); init_tcp_driver = false; } static boolean SOCK_GetAddr(mysockaddr_t *sin, const char *address, const char *port, boolean test) { addrinfo_t *ai, *runp, hints; int gaie; size_t i; if (!port || !port[0]) port = DEFAULTPORT; memset(&hints, 0x00, sizeof (hints)); hints.ai_flags = AI_ADDRCONFIG; hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; hints.ai_protocol = IPPROTO_UDP; gaie = getaddrinfo(address, port, &hints, &ai); if (gaie != 0) return false; runp = ai; if (test) { while (runp != NULL) { // test ip address of server for (i = 0; i < mysocketses; ++i) { if (runp->ai_addr->sa_family == myfamily[i]) { break; } } if (i >= mysocketses) runp = runp->ai_next; else break; } } if (runp != NULL) memcpy(sin, runp->ai_addr, runp->ai_addrlen); freeaddrinfo(ai); return (runp != NULL); } static SINT8 SOCK_NetMakeNodewPort(const char *address, const char *port) { SINT8 newnode = getfreenode(); DEBFILE(va("Creating new node: %s@%s\n", address, port)); if (newnode != -1) { if (!SOCK_GetAddr(&clientaddress[newnode], address, port, true)) { nodeconnected[newnode] = false; return -1; } } return newnode; } /* See ../doc/Holepunch-Protocol.txt */ #ifdef HOLEPUNCH static void rendezvous(int size) { char *addrs = strdup(cv_rendezvousserver.string); char *host = strtok(addrs, ":"); char *port = strtok(NULL, ":"); static mysockaddr_t rzv; static tic_t refreshtic = (tic_t)-1; tic_t tic = I_GetTime(); holepunch_t *holepunchpacket = HOLEPUNCH_DATA(doomcom); if (tic != refreshtic) { if (SOCK_GetAddr(&rzv, host, (port ? port : "7777"), false)) { refreshtic = tic; } else { CONS_Alert(CONS_ERROR, "Failed to contact rendezvous server (%s).\n", cv_rendezvousserver.string); } } if (tic == refreshtic) { holepunchpacket->magic = hole_punch_magic; sendto(mysockets[0], doomcom->data, size, 0, &rzv.any, sizeof rzv.ip4); } free(addrs); } static void SOCK_RequestHolePunch(INT32 node) { mysockaddr_t * addr = &clientaddress[node]; holepunch_t *holepunchpacket = HOLEPUNCH_DATA(doomcom); holepunchpacket->addr = addr->ip4.sin_addr.s_addr; holepunchpacket->port = addr->ip4.sin_port; CONS_Debug(DBG_NETPLAY, "requesting hole punch to node %s\n", SOCK_AddrToStr(addr)); rendezvous(10); } static void SOCK_RegisterHolePunch(void) { rendezvous(4); } #endif static boolean SOCK_OpenSocket(void) { memset(clientaddress, 0, sizeof(clientaddress)); memset(nodeconnected, false, sizeof(nodeconnected)); nodeconnected[0] = true; // always connected to self nodeconnected[BROADCASTADDR] = true; I_NetSend = SOCK_Send; I_NetGet = SOCK_Get; I_NetCloseSocket = SOCK_CloseSocket; I_NetFreeNodenum = SOCK_FreeNodenum; I_NetMakeNodewPort = SOCK_NetMakeNodewPort; #ifdef HOLEPUNCH I_NetRequestHolePunch = SOCK_RequestHolePunch; I_NetRegisterHolePunch = SOCK_RegisterHolePunch; #endif // build the socket but close it first SOCK_CloseSocket(); return UDP_Socket(); } static void AddBannedIndex(void) { if (numbans >= banned_size) { if (banned_size == 0) { banned_size = 8; } else { banned_size *= 2; } banned = Z_Realloc((void*) banned, sizeof(banned_t) * banned_size, PU_STATIC, NULL); } numbans++; } static boolean SOCK_Ban(INT32 node) { INT32 ban; if (node < 0 || node > MAXNETNODES) return false; ban = numbans; AddBannedIndex(); memcpy(&banned[ban].address, &clientaddress[node], sizeof (mysockaddr_t)); if (banned[ban].address.any.sa_family == AF_INET) { banned[ban].address.ip4.sin_port = 0; banned[ban].mask = 32; } #ifdef HAVE_IPV6 else if (banned[ban].address.any.sa_family == AF_INET6) { banned[ban].address.ip6.sin6_port = 0; banned[ban].mask = 128; } #endif return true; } static boolean SOCK_SetBanUsername(const char *username) { if (username == NULL || strlen(username) == 0) { username = "Direct IP ban"; } if (banned[numbans - 1].username) { Z_Free(banned[numbans - 1].username); banned[numbans - 1].username = NULL; } banned[numbans - 1].username = Z_StrDup(username); return true; } static boolean SOCK_SetBanReason(const char *reason) { if (reason == NULL || strlen(reason) == 0) { reason = "No reason given"; } if (banned[numbans - 1].reason) { Z_Free(banned[numbans - 1].reason); banned[numbans - 1].reason = NULL; } banned[numbans - 1].reason = Z_StrDup(reason); return true; } static boolean SOCK_SetUnbanTime(time_t timestamp) { banned[numbans - 1].timestamp = timestamp; return true; } static boolean SOCK_SetBanAddress(const char *address, const char *mask) { addrinfo_t *ai, *runp, hints; int gaie; if (!address) return false; memset(&hints, 0x00, sizeof(hints)); hints.ai_flags = 0; hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; hints.ai_protocol = IPPROTO_UDP; gaie = getaddrinfo(address, "0", &hints, &ai); if (gaie != 0) return false; runp = ai; while (runp != NULL) { INT32 ban; UINT8 numericalmask; ban = numbans; AddBannedIndex(); memcpy(&banned[ban].address, runp->ai_addr, runp->ai_addrlen); #ifdef HAVE_IPV6 if (runp->ai_family == AF_INET6) banned[ban].mask = 128; else #endif banned[ban].mask = 32; if (mask) { numericalmask = (UINT8)atoi(mask); } else { numericalmask = 0; } if (numericalmask > 0 && numericalmask < banned[ban].mask) { banned[ban].mask = numericalmask; } // Set defaults, in case anything funny happens. SOCK_SetBanUsername(NULL); SOCK_SetBanReason(NULL); SOCK_SetUnbanTime(NO_BAN_TIME); runp = runp->ai_next; } freeaddrinfo(ai); return true; } static void SOCK_ClearBans(void) { numbans = 0; banned_size = 0; Z_Free(banned); banned = NULL; } boolean I_InitTcpNetwork(void) { char serverhostname[255]; boolean ret = false; // initilize the OS's TCP/IP stack if (!I_InitTcpDriver()) return false; if (M_CheckParm("-port") || M_CheckParm("-serverport")) // Combined -udpport and -clientport into -port // As it was really redundant having two seperate parms that does the same thing /* Sorry Steel, I'm adding these back. But -udpport is a stupid name. */ { /* If it's NULL, that's okay! Because then we'll get a random port from getaddrinfo. */ serverport_name = M_GetNextParm(); } if (M_CheckParm("-clientport")) clientport_name = M_GetNextParm(); // parse network game options, if (M_CheckParm("-server") || dedicated) { server = true; // If a number of clients (i.e. nodes) is specified, the server will wait for the clients // to connect before starting. // If no number is specified here, the server starts with 1 client, and others can join // in-game. // Since Boris has implemented join in-game, there is no actual need for specifying a // particular number here. // FIXME: for dedicated server, numnodes needs to be set to 0 upon start if (dedicated) doomcom->numnodes = 0; else doomcom->numnodes = 1; if (doomcom->numnodes < 0) doomcom->numnodes = 0; if (doomcom->numnodes > MAXNETNODES) doomcom->numnodes = MAXNETNODES; // server servernode = 0; // FIXME: // ??? and now ? // server on a big modem ??? 4*isdn hardware_MAXPACKETLENGTH = INETPACKETLENGTH; ret = true; } else if (M_CheckParm("-connect")) { if (M_IsNextParm()) strcpy(serverhostname, M_GetNextParm()); else serverhostname[0] = 0; // assuming server in the LAN, use broadcast to detect it // server address only in ip if (serverhostname[0]) { COM_BufAddText("connect \""); COM_BufAddText(serverhostname); COM_BufAddText("\"\n"); // probably modem hardware_MAXPACKETLENGTH = INETPACKETLENGTH; } else { // so we're on a LAN COM_BufAddText("connect any\n"); hardware_MAXPACKETLENGTH = MAXPACKETLENGTH; } } if (M_CheckProtoParam("ip")) { COM_ImmedExecute(va("exec \"%s"PATHSEP"kartexec.cfg\" -noerror\n", srb2home)); strcpy(serverhostname, M_GetProtoParam()); // server address only in ip if (serverhostname[0]) { CONS_Printf("SERVERNAME: %s\n", serverhostname); COM_BufAddText("connect \""); COM_BufAddText(serverhostname); COM_BufAddText("\"\n"); // probably modem hardware_MAXPACKETLENGTH = INETPACKETLENGTH; } else { // so we're on a LAN COM_BufAddText("connect any\n"); hardware_MAXPACKETLENGTH = MAXPACKETLENGTH; } } I_NetOpenSocket = SOCK_OpenSocket; I_Ban = SOCK_Ban; I_ClearBans = SOCK_ClearBans; I_GetNodeAddress = SOCK_GetNodeAddress; I_GetBanAddress = SOCK_GetBanAddress; I_GetBanMask = SOCK_GetBanMask; I_GetBanUsername = SOCK_GetBanUsername; I_GetBanReason = SOCK_GetBanReason; I_GetUnbanTime = SOCK_GetUnbanTime; I_SetBanAddress = SOCK_SetBanAddress; I_SetBanUsername = SOCK_SetBanUsername; I_SetBanReason = SOCK_SetBanReason; I_SetUnbanTime = SOCK_SetUnbanTime; bannednode = SOCK_bannednode; return ret; }