/* Copyright (C) 1996-1997 Id Software, Inc. 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. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // net_wins.c struct sockaddr; #include "quakedef.h" #include "netinc.h" #ifdef _WIN32 #define USE_GETHOSTNAME_LOCALLISTING #endif netadr_t net_local_cl_ipadr; //still used to match local ui requests (quake/gamespy), and to generate ip reports for q3 servers (which is probably pointless). netadr_t net_from; sizebuf_t net_message; //#define MAX_UDP_PACKET (MAX_MSGLEN*2) // one more than msg + header #define MAX_UDP_PACKET 8192 // one more than msg + header qbyte net_message_buffer[MAX_OVERALLMSGLEN]; #ifdef _WIN32 WSADATA winsockdata; #endif #ifdef IPPROTO_IPV6 #ifdef _WIN32 int (WINAPI *pgetaddrinfo) ( const char* nodename, const char* servname, const struct addrinfo* hints, struct addrinfo** res ); void (WSAAPI *pfreeaddrinfo) (struct addrinfo*); #else #define pgetaddrinfo getaddrinfo #define pfreeaddrinfo freeaddrinfo /*int (*pgetaddrinfo) ( const char* nodename, const char* servname, const struct addrinfo* hints, struct addrinfo** res ); void (*pfreeaddrinfo) (struct addrinfo*); */ #endif #endif #if defined(HAVE_IPV4) && !defined(CLIENTONLY) #define HAVE_NATPMP #endif void NET_GetLocalAddress (int socket, netadr_t *out); //int TCP_OpenListenSocket (const char *localip, int port); #ifdef IPPROTO_IPV6 int UDP6_OpenSocket (int port, qboolean bcast); #endif #ifdef USEIPX void IPX_CloseSocket (int socket); #endif cvar_t net_hybriddualstack = CVAR("net_hybriddualstack", "1"); cvar_t net_fakeloss = CVARFD("net_fakeloss", "0", CVAR_CHEAT, "Simulates packetloss in both receiving and sending, on a scale from 0 to 1."); extern cvar_t sv_public, sv_listen_qw, sv_listen_nq, sv_listen_dp, sv_listen_q3; static qboolean allowconnects = false; #define FTENET_ADDRTYPES 2 typedef struct ftenet_generic_connection_s { char name[MAX_QPATH]; int (*GetLocalAddress)(struct ftenet_generic_connection_s *con, netadr_t *local, int adridx); qboolean (*ChangeLocalAddress)(struct ftenet_generic_connection_s *con, const char *newaddress); qboolean (*GetPacket)(struct ftenet_generic_connection_s *con); qboolean (*SendPacket)(struct ftenet_generic_connection_s *con, int length, void *data, netadr_t *to); void (*Close)(struct ftenet_generic_connection_s *con); #ifdef HAVE_PACKET int (*SetReceiveFDSet) (struct ftenet_generic_connection_s *con, fd_set *fdset); /*set for connections which have multiple sockets (ie: listening tcp connections)*/ #endif netadrtype_t addrtype[FTENET_ADDRTYPES]; qboolean islisten; SOCKET thesocket; } ftenet_generic_connection_t; #define MAX_LOOPBACK 8 typedef struct { qbyte data[MAX_OVERALLMSGLEN]; int datalen; } loopmsg_t; typedef struct { qboolean inited; loopmsg_t msgs[MAX_LOOPBACK]; int get, send; } loopback_t; loopback_t loopbacks[2]; //============================================================================= int NetadrToSockadr (netadr_t *a, struct sockaddr_qstorage *s) { switch(a->type) { #ifdef HAVE_WEBSOCKCL case NA_WEBSOCKET: memset (s, 0, sizeof(struct sockaddr_websocket)); ((struct sockaddr_websocket*)s)->sws_family = AF_WEBSOCK; memcpy(((struct sockaddr_websocket*)s)->url, a->address.websocketurl, sizeof(((struct sockaddr_websocket*)s)->url)); return sizeof(struct sockaddr_websocket); #endif #ifdef HAVE_IPV4 case NA_BROADCAST_IP: memset (s, 0, sizeof(struct sockaddr_in)); ((struct sockaddr_in*)s)->sin_family = AF_INET; *(int *)&((struct sockaddr_in*)s)->sin_addr = 0xffffffff;//INADDR_BROADCAST; ((struct sockaddr_in*)s)->sin_port = a->port; return sizeof(struct sockaddr_in); case NA_TCP: case NA_IP: memset (s, 0, sizeof(struct sockaddr_in)); ((struct sockaddr_in*)s)->sin_family = AF_INET; *(int *)&((struct sockaddr_in*)s)->sin_addr = *(int *)&a->address.ip; ((struct sockaddr_in*)s)->sin_port = a->port; return sizeof(struct sockaddr_in); #endif #ifdef IPPROTO_IPV6 case NA_BROADCAST_IP6: memset (s, 0, sizeof(struct sockaddr_in)); ((struct sockaddr_in6*)s)->sin6_family = AF_INET6; memset((int *)&((struct sockaddr_in6*)s)->sin6_addr, 0, sizeof(*(int *)&((struct sockaddr_in6*)s)->sin6_addr)); ((struct sockaddr_in6*)s)->sin6_addr.s6_addr[0] = 0xff; ((struct sockaddr_in6*)s)->sin6_addr.s6_addr[1] = 0x02; ((struct sockaddr_in6*)s)->sin6_addr.s6_addr[15] = 0x01; ((struct sockaddr_in6*)s)->sin6_port = a->port; return sizeof(struct sockaddr_in6); case NA_TCPV6: case NA_IPV6: memset (s, 0, sizeof(struct sockaddr_in6)); ((struct sockaddr_in6*)s)->sin6_family = AF_INET6; memcpy(&((struct sockaddr_in6*)s)->sin6_addr, a->address.ip6, sizeof(struct in6_addr)); ((struct sockaddr_in6*)s)->sin6_port = a->port; return sizeof(struct sockaddr_in6); #endif #ifdef USEIPX case NA_IPX: ((struct sockaddr_ipx *)s)->sa_family = AF_IPX; memcpy(((struct sockaddr_ipx *)s)->sa_netnum, &a->address.ipx[0], 4); memcpy(((struct sockaddr_ipx *)s)->sa_nodenum, &a->address.ipx[4], 6); ((struct sockaddr_ipx *)s)->sa_socket = a->port; return sizeof(struct sockaddr_ipx); case NA_BROADCAST_IPX: memset (s, 0, sizeof(struct sockaddr_ipx)); ((struct sockaddr_ipx*)s)->sa_family = AF_IPX; memset(&((struct sockaddr_ipx*)s)->sa_netnum, 0, 4); memset(&((struct sockaddr_ipx*)s)->sa_nodenum, 0xff, 6); ((struct sockaddr_ipx*)s)->sa_socket = a->port; return sizeof(struct sockaddr_ipx); #endif default: Sys_Error("Bad type - needs fixing"); return 0; } } void SockadrToNetadr (struct sockaddr_qstorage *s, netadr_t *a) { a->connum = 0; switch (((struct sockaddr*)s)->sa_family) { #ifdef HAVE_WEBSOCKCL case AF_WEBSOCK: a->type = NA_WEBSOCKET; memcpy(a->address.websocketurl, ((struct sockaddr_websocket*)s)->url, sizeof(a->address.websocketurl)); a->port = 0; break; #endif #ifdef HAVE_IPV4 case AF_INET: a->type = NA_IP; *(int *)&a->address.ip = ((struct sockaddr_in *)s)->sin_addr.s_addr; a->port = ((struct sockaddr_in *)s)->sin_port; break; #endif #ifdef IPPROTO_IPV6 case AF_INET6: a->type = NA_IPV6; memcpy(&a->address.ip6, &((struct sockaddr_in6 *)s)->sin6_addr, sizeof(a->address.ip6)); a->port = ((struct sockaddr_in6 *)s)->sin6_port; break; #endif #ifdef USEIPX case AF_IPX: a->type = NA_IPX; *(int *)a->address.ip = 0xffffffff; memcpy(&a->address.ipx[0], ((struct sockaddr_ipx *)s)->sa_netnum, 4); memcpy(&a->address.ipx[4], ((struct sockaddr_ipx *)s)->sa_nodenum, 6); a->port = ((struct sockaddr_ipx *)s)->sa_socket; break; #endif default: Con_Printf("SockadrToNetadr: bad socket family - %i", ((struct sockaddr*)s)->sa_family); case AF_UNSPEC: memset(a, 0, sizeof(*a)); a->type = NA_INVALID; break; } } qboolean NET_CompareAdr (netadr_t *a, netadr_t *b) { if (a->type != b->type) { int i; if (a->type == NA_IP && b->type == NA_IPV6) { for (i = 0; i < 10; i++) if (b->address.ip6[i] != 0) return false; //only matches if they're 0s, otherwise its not an ipv4 address there for (; i < 12; i++) if (b->address.ip6[i] != 0xff && b->address.ip6[i] != 0x00) //0x00 is depricated return false; //only matches if they're 0s or ffs, otherwise its not an ipv4 address there for (i = 0; i < 4; i++) { if (a->address.ip[i] != b->address.ip6[12+i]) return false; //mask doesn't match } return true; //its an ipv4 address in there, the mask matched the whole way through } if (a->type == NA_IPV6 && b->type == NA_IP) { for (i = 0; i < 10; i++) if (a->address.ip6[i] != 0) return false; //only matches if they're 0s, otherwise its not an ipv4 address there for (; i < 12; i++) if (a->address.ip6[i] != 0xff && a->address.ip6[i] != 0x00) //0x00 is depricated return false; //only matches if they're 0s or ffs, otherwise its not an ipv4 address there for (i = 0; i < 4; i++) { if (a->address.ip6[12+i] != b->address.ip[i]) return false; //mask doesn't match } return true; //its an ipv4 address in there, the mask matched the whole way through } return false; } if (a->type == NA_LOOPBACK) return true; #ifdef HAVE_WEBSOCKCL if (a->type == NA_WEBSOCKET) { if (!strcmp(a->address.websocketurl, a->address.websocketurl) && a->port == b->port) return true; return false; } #endif #ifdef HAVE_IPV4 if (a->type == NA_IP || a->type == NA_BROADCAST_IP || a->type == NA_TCP) { if ((memcmp(a->address.ip, b->address.ip, sizeof(a->address.ip)) == 0) && a->port == b->port) return true; return false; } #endif #ifdef IPPROTO_IPV6 if (a->type == NA_IPV6 || a->type == NA_BROADCAST_IP6 || a->type == NA_TCPV6) { if ((memcmp(a->address.ip6, b->address.ip6, sizeof(a->address.ip6)) == 0) && a->port == b->port) return true; return false; } #endif #ifdef USEIPX if (a->type == NA_IPX || a->type == NA_BROADCAST_IPX) { if ((memcmp(a->address.ipx, b->address.ipx, sizeof(a->address.ipx)) == 0) && a->port == b->port) return true; return false; } #endif #ifdef IRCCONNECT if (a->type == NA_IRC) { if (!strcmp(a->address.irc.user, b->address.irc.user)) return true; return false; } #endif Sys_Error("NET_CompareAdr: Bad address type"); return false; } /* =================== NET_CompareBaseAdr Compares without the port =================== */ qboolean NET_CompareBaseAdr (netadr_t *a, netadr_t *b) { if (a->type != b->type) return false; if (a->type == NA_LOOPBACK) return true; #ifdef HAVE_IPV4 if (a->type == NA_IP || a->type == NA_TCP) { if ((memcmp(a->address.ip, b->address.ip, sizeof(a->address.ip)) == 0)) return true; return false; } #endif #ifdef IPPROTO_IPV6 if (a->type == NA_IPV6 || a->type == NA_BROADCAST_IP6) { if ((memcmp(a->address.ip6, b->address.ip6, 16) == 0)) return true; return false; } #endif #ifdef USEIPX if (a->type == NA_IPX) { if ((memcmp(a->address.ipx, b->address.ipx, 10) == 0)) return true; return false; } #endif #ifdef IRCCONNECT if (a->type == NA_IRC) { if (!strcmp(a->address.irc.user, b->address.irc.user)) return true; return false; } #endif Sys_Error("NET_CompareBaseAdr: Bad address type"); return false; } qboolean NET_AddressSmellsFunny(netadr_t *a) { #ifdef IPPROTO_IPV6 int i; #endif //rejects certain blacklisted addresses switch(a->type) { #ifdef HAVE_IPV4 case NA_BROADCAST_IP: case NA_IP: //reject localhost if (a->address.ip[0] == 127)// && a->address.ip[1] == 0 && a->address.ip[2] == 0 && a->address.ip[3] == 1 ) return true; //'this' network (not an issue, but lets reject it anyway) if (a->address.ip[0] == 0 && a->address.ip[1] == 0 && a->address.ip[2] == 0 && a->address.ip[3] == 0 ) return true; //reject any broadcasts if (a->address.ip[0] == 255 && a->address.ip[1] == 255 && a->address.ip[2] == 255 && a->address.ip[3] == 0 ) return true; //not much else I can reject return false; #endif #ifdef IPPROTO_IPV6 case NA_BROADCAST_IP6: case NA_IPV6: //reject [::XXXX] (this includes obsolete ipv4-compatible (not ipv4 mapped), and localhost) for (i = 0; i < 12; i++) if (a->address.ip6[i]) break; if (i == 12) return true; return false; #endif #ifdef USEIPX //no idea how this protocol's addresses work case NA_BROADCAST_IPX: case NA_IPX: return false; #endif case NA_LOOPBACK: return false; default: return true; } } char *NET_AdrToString (char *s, int len, netadr_t *a) { char *rs = s; char *p; int i; #ifdef IPPROTO_IPV6 qboolean doneblank; #endif switch(a->type) { #ifdef HAVE_WEBSOCKCL case NA_WEBSOCKET: Q_strncpyz(s, a->address.websocketurl, len); break; #endif #ifdef TCPCONNECT case NA_TCP: if (len < 7) return "?"; snprintf (s, len, "tcp://"); s += 6; len -= 6; //fallthrough #endif #ifdef HAVE_IPV4 case NA_BROADCAST_IP: case NA_IP: if (a->port) { snprintf (s, len, "%i.%i.%i.%i:%i", a->address.ip[0], a->address.ip[1], a->address.ip[2], a->address.ip[3], ntohs(a->port)); } else { snprintf (s, len, "%i.%i.%i.%i", a->address.ip[0], a->address.ip[1], a->address.ip[2], a->address.ip[3]); } break; #endif #ifdef TCPCONNECT case NA_TCPV6: if (len < 7) return "?"; snprintf (s, len, "tcp://"); s += 6; len -= 6; //fallthrough #endif #ifdef IPPROTO_IPV6 case NA_BROADCAST_IP6: case NA_IPV6: if (!*(int*)&a->address.ip6[0] && !*(int*)&a->address.ip6[4] && !*(short*)&a->address.ip6[8] && *(short*)&a->address.ip6[10] == (short)0xffff) { if (a->port) snprintf (s, len, "%i.%i.%i.%i:%i", a->address.ip6[12], a->address.ip6[13], a->address.ip6[14], a->address.ip6[15], ntohs(a->port)); else snprintf (s, len, "%i.%i.%i.%i", a->address.ip6[12], a->address.ip6[13], a->address.ip6[14], a->address.ip6[15]); break; } *s = 0; doneblank = false; p = s; if (a->port) { snprintf (s, len-strlen(s), "["); p += strlen(p); } for (i = 0; i < 16; i+=2) { if (doneblank!=true && a->address.ip6[i] == 0 && a->address.ip6[i+1] == 0) { if (!doneblank) { snprintf (p, len-strlen(s), "::"); p += strlen(p); doneblank = 2; } } else { if (doneblank==2) doneblank = true; else if (i != 0) { snprintf (p, len-strlen(s), ":"); p += strlen(p); } if (a->address.ip6[i+0]) { snprintf (p, len-strlen(s), "%x%02x", a->address.ip6[i+0], a->address.ip6[i+1]); } else { snprintf (p, len-strlen(s), "%x", a->address.ip6[i+1]); } p += strlen(p); } } if (a->port) snprintf (p, len-strlen(s), "]:%i", ntohs(a->port)); break; #endif #ifdef USEIPX case NA_BROADCAST_IPX: case NA_IPX: snprintf (s, len, "%02x%02x%02x%02x:%02x%02x%02x%02x%02x%02x:%i", a->address.ipx[0], a->address.ipx[1], a->address.ipx[2], a->address.ipx[3], a->address.ipx[4], a->address.ipx[5], a->address.ipx[6], a->address.ipx[7], a->address.ipx[8], a->address.ipx[9], ntohs(a->port)); break; #endif case NA_LOOPBACK: snprintf (s, len, "QLoopBack"); break; #ifdef IRCCONNECT case NA_IRC: if (*a->address.irc.channel) snprintf (s, len, "irc://%s@%s", a->address.irc.user, a->address.irc.channel); else snprintf (s, len, "irc://%s", a->address.irc.user); break; #endif default: snprintf (s, len, "invalid netadr_t type"); // Sys_Error("NET_AdrToString: Bad netadr_t type"); } return rs; } char *NET_BaseAdrToString (char *s, int len, netadr_t *a) { int i, doneblank; char *p; switch(a->type) { case NA_BROADCAST_IP: case NA_IP: snprintf (s, len, "%i.%i.%i.%i", a->address.ip[0], a->address.ip[1], a->address.ip[2], a->address.ip[3]); break; case NA_TCP: snprintf (s, len, "tcp://%i.%i.%i.%i", a->address.ip[0], a->address.ip[1], a->address.ip[2], a->address.ip[3]); break; #ifdef IPPROTO_IPV6 case NA_BROADCAST_IP6: case NA_IPV6: if (!*(int*)&a->address.ip6[0] && !*(int*)&a->address.ip6[4] && !*(short*)&a->address.ip6[8] && *(short*)&a->address.ip6[10] == (short)0xffff) { snprintf (s, len, "%i.%i.%i.%i", a->address.ip6[12], a->address.ip6[13], a->address.ip6[14], a->address.ip6[15]); break; } *s = 0; doneblank = false; p = s; for (i = 0; i < 16; i+=2) { if (doneblank!=true && a->address.ip6[i] == 0 && a->address.ip6[i+1] == 0) { if (!doneblank) { snprintf (p, len-strlen(s), "::"); p += strlen(p); doneblank = 2; } } else { if (doneblank==2) doneblank = true; else if (i != 0) { snprintf (p, len-strlen(s), ":"); p += strlen(p); } if (a->address.ip6[i+0]) { snprintf (p, len-strlen(s), "%x%02x", a->address.ip6[i+0], a->address.ip6[i+1]); } else { snprintf (p, len-strlen(s), "%x", a->address.ip6[i+1]); } p += strlen(p); } } break; #endif #ifdef USEIPX case NA_BROADCAST_IPX: case NA_IPX: snprintf (s, len, "%02x%02x%02x%02x:%02x%02x%02x%02x%02x%02x", a->address.ipx[0], a->address.ipx[1], a->address.ipx[2], a->address.ipx[3], a->address.ipx[4], a->address.ipx[5], a->address.ipx[6], a->address.ipx[7], a->address.ipx[8], a->address.ipx[9]); break; #endif case NA_LOOPBACK: snprintf (s, len, "QLoopBack"); break; #ifdef IRCCONNECT case NA_IRC: NET_AdrToString(s, len, a); break; #endif default: Sys_Error("NET_BaseAdrToString: Bad netadr_t type"); } return s; } /* ============= NET_StringToAdr idnewt idnewt:28000 192.246.40.70 192.246.40.70:28000 any form of ipv6, including port number. ============= */ qboolean NET_StringToSockaddr (const char *s, int defaultport, struct sockaddr_qstorage *sadr, int *addrfamily, int *addrsize) { struct hostent *h; char *colon; char copy[128]; if (!(*s)) return false; memset (sadr, 0, sizeof(*sadr)); #ifdef USEIPX if ((strlen(s) >= 23) && (s[8] == ':') && (s[21] == ':')) // check for an IPX address { unsigned int val; ((struct sockaddr_ipx *)sadr)->sa_family = AF_IPX; #define DO(src,dest) \ copy[0] = s[src]; \ copy[1] = s[src + 1]; \ sscanf (copy, "%x", &val); \ ((struct sockaddr_ipx *)sadr)->dest = val copy[2] = 0; DO(0, sa_netnum[0]); DO(2, sa_netnum[1]); DO(4, sa_netnum[2]); DO(6, sa_netnum[3]); DO(9, sa_nodenum[0]); DO(11, sa_nodenum[1]); DO(13, sa_nodenum[2]); DO(15, sa_nodenum[3]); DO(17, sa_nodenum[4]); DO(19, sa_nodenum[5]); sscanf (&s[22], "%u", &val); #undef DO ((struct sockaddr_ipx *)sadr)->sa_socket = htons((unsigned short)val); if (addrfamily) *addrfamily = AF_IPX; if (addrsize) *addrsize = sizeof(struct sockaddr_ipx); } else #endif #ifdef IPPROTO_IPV6 #ifdef pgetaddrinfo if (1) #else if (pgetaddrinfo) #endif { struct addrinfo *addrinfo = NULL; struct addrinfo *pos; struct addrinfo udp6hint; int error; char *port; char dupbase[256]; int len; memset(&udp6hint, 0, sizeof(udp6hint)); udp6hint.ai_family = 0;//Any... we check for AF_INET6 or 4 udp6hint.ai_socktype = SOCK_DGRAM; udp6hint.ai_protocol = IPPROTO_UDP; if (*s == '[') { port = strstr(s, "]"); if (!port) error = EAI_NONAME; else { len = port - (s+1); if (len >= sizeof(dupbase)) len = sizeof(dupbase)-1; strncpy(dupbase, s+1, len); dupbase[len] = '\0'; error = pgetaddrinfo(dupbase, (port[1] == ':')?port+2:NULL, &udp6hint, &addrinfo); } } else { port = strrchr(s, ':'); if (port) { len = port - s; if (len >= sizeof(dupbase)) len = sizeof(dupbase)-1; strncpy(dupbase, s, len); dupbase[len] = '\0'; error = pgetaddrinfo(dupbase, port+1, &udp6hint, &addrinfo); } else error = EAI_NONAME; if (error) //failed, try string with no port. error = pgetaddrinfo(s, NULL, &udp6hint, &addrinfo); //remember, this func will return any address family that could be using the udp protocol... (ip4 or ip6) } if (error) { return false; } ((struct sockaddr*)sadr)->sa_family = 0; for (pos = addrinfo; pos; pos = pos->ai_next) { switch(pos->ai_family) { case AF_INET6: if (((struct sockaddr_in *)sadr)->sin_family == AF_INET6) break; //first one should be best... //fallthrough #ifdef HAVE_IPV4 case AF_INET: memcpy(sadr, pos->ai_addr, pos->ai_addrlen); if (pos->ai_family == AF_INET) goto dblbreak; //don't try finding any more, this is quake, they probably prefer ip4... break; #else memcpy(sadr, pos->ai_addr, pos->ai_addrlen); goto dblbreak; #endif } } dblbreak: pfreeaddrinfo (addrinfo); if (!((struct sockaddr*)sadr)->sa_family) //none suitablefound return false; if (addrfamily) *addrfamily = ((struct sockaddr*)sadr)->sa_family; if (((struct sockaddr*)sadr)->sa_family == AF_INET) { if (!((struct sockaddr_in *)sadr)->sin_port) ((struct sockaddr_in *)sadr)->sin_port = htons(defaultport); if (addrsize) *addrsize = sizeof(struct sockaddr_in); } else { if (!((struct sockaddr_in6 *)sadr)->sin6_port) ((struct sockaddr_in6 *)sadr)->sin6_port = htons(defaultport); if (addrsize) *addrsize = sizeof(struct sockaddr_in6); } } else #endif { #ifdef HAVE_IPV4 ((struct sockaddr_in *)sadr)->sin_family = AF_INET; ((struct sockaddr_in *)sadr)->sin_port = 0; if (strlen(s) >= sizeof(copy)-1) return false; ((struct sockaddr_in *)sadr)->sin_port = htons(defaultport); strcpy (copy, s); // strip off a trailing :port if present for (colon = copy ; *colon ; colon++) if (*colon == ':') { *colon = 0; ((struct sockaddr_in *)sadr)->sin_port = htons((short)atoi(colon+1)); } if (copy[0] >= '0' && copy[0] <= '9') //this is the wrong way to test. a server name may start with a number. { *(int *)&((struct sockaddr_in *)sadr)->sin_addr = inet_addr(copy); } else { if (! (h = gethostbyname(copy)) ) return false; if (h->h_addrtype != AF_INET) return false; *(int *)&((struct sockaddr_in *)sadr)->sin_addr = *(int *)h->h_addr_list[0]; } if (addrfamily) *addrfamily = AF_INET; if (addrsize) *addrsize = sizeof(struct sockaddr_in); #else return false; #endif } return true; } /* accepts anything that NET_StringToSockaddr accepts plus certain url schemes including: tcp, irc */ qboolean NET_StringToAdr (const char *s, int defaultport, netadr_t *a) { struct sockaddr_qstorage sadr; Con_DPrintf("Resolving address: %s\n", s); if (!strcmp (s, "internalserver")) { memset (a, 0, sizeof(*a)); a->type = NA_LOOPBACK; return true; } #ifdef HAVE_WEBSOCKCL if (!strncmp (s, "ws://", 5) || !strncmp (s, "wss://", 6)) { memset (a, 0, sizeof(*a)); a->type = NA_WEBSOCKET; Q_strncpyz(a->address.websocketurl, s, sizeof(a->address.websocketurl)); return true; } else { /*code for convienience - no other protocols work anyway*/ static float warned; if (warned < realtime) { Con_Printf("Note: Assuming ws:// prefix\n"); warned = realtime + 1; } memset (a, 0, sizeof(*a)); a->type = NA_WEBSOCKET; memcpy(a->address.websocketurl, "ws://", 5); Q_strncpyz(a->address.websocketurl+5, s, sizeof(a->address.websocketurl)-5); return true; } #endif #ifdef TCPCONNECT if (!strncmp (s, "tcp://", 6)) { //make sure that the rest of the address is a valid ip address (4 or 6) if (!NET_StringToSockaddr (s+6, defaultport, &sadr, NULL, NULL)) { a->type = NA_INVALID; return false; } SockadrToNetadr (&sadr, a); if (a->type == NA_IP) { a->type = NA_TCP; return true; } if (a->type == NA_IPV6) { a->type = NA_TCPV6; return true; } return false; } #endif #ifdef IRCCONNECT if (!strncmp (s, "irc://", 6)) { char *at; char *slash; memset (a, 0, sizeof(*a)); a->type = NA_IRC; s+=6; slash = strchr(s, '/'); if (!slash) return false; if (slash - s+1 >= sizeof(a->address.irc.host)) return false; memcpy(a->address.irc.host, s, slash - s); a->address.irc.host[slash - s] = 0; s = slash+1; at = strchr(s, '@'); if (at) { if (at-s+1 >= sizeof(a->address.irc.user)) return false; Q_strncpyz(a->address.irc.user, s, at-s+1); Q_strncpyz(a->address.irc.channel, at+1, sizeof(a->address.irc.channel)); } else { //just a user. Q_strncpyz(a->address.irc.user, s, sizeof(a->address.irc.user)); } return true; } #endif #ifdef HAVE_NATPMP if (!strncmp (s, "natpmp://", 9)) { NET_PortToAdr(NA_NATPMP, s+9, a); if (a->type == NA_IP) a->type = NA_NATPMP; if (a->type != NA_NATPMP) return false; return true; } #endif if (!NET_StringToSockaddr (s, defaultport, &sadr, NULL, NULL)) { a->type = NA_INVALID; return false; } SockadrToNetadr (&sadr, a); #if !defined(HAVE_PACKET) && defined(HAVE_TCP) //bump over protocols that cannot work in the first place. if (a->type == NA_IP) a->type = NA_TCP; if (a->type == NA_IPV6) a->type = NA_TCPV6; #endif return true; } // NET_IntegerToMask: given a source address pointer, a mask address pointer, and // desired number of bits, fills the mask pointer with given bits // (bits < 0 will always fill all bits) void NET_IntegerToMask (netadr_t *a, netadr_t *amask, int bits) { unsigned int i; qbyte *n; memset (amask, 0, sizeof(*amask)); amask->type = a->type; if (bits < 0) i = 8000; // fill all bits else i = bits; switch (amask->type) { case NA_INVALID: break; case NA_IP: case NA_BROADCAST_IP: n = amask->address.ip; if (i > 32) i = 32; for (; i >= 8; i -= 8) { *n = 0xFF; n++; } // fill last bit if (i) { i = 8 - i; i = 255 - ((1 << i) - 1); *n = i; } break; case NA_IPV6: case NA_BROADCAST_IP6: #ifdef IPPROTO_IPV6 n = amask->address.ip6; if (i > 128) i = 128; for (; i >= 8; i -= 8) { *n = 0xFF; n++; } // fill last bit if (i) { i = 8 - i; i = 255 - ((1 << i) - 1); *n = i; } #endif break; case NA_IPX: case NA_BROADCAST_IPX: #ifdef USEIPX n = amask->address.ipx; if (i > 80) i = 80; for (; i >= 8; i -= 8) { *n = 0xFF; n++; } // fill last bit if (i) { i = 8 - i; i = 255 - ((1 << i) - 1); *n = i; } #endif break; case NA_LOOPBACK: break; // warning: enumeration value âNA_*â not handled in switch case NA_NATPMP: case NA_WEBSOCKET: case NA_TCP: case NA_TCPV6: case NA_IRC: break; } } // ParsePartialIPv4: check string to see if it is a partial IPv4 address and // return bits to mask and set netadr_t or 0 if not an address int ParsePartialIPv4(const char *s, netadr_t *a) { const char *colon = NULL; char *address = a->address.ip; int bits = 8; if (!*s) return 0; memset (a, 0, sizeof(*a)); while (*s) { if (*s == ':') { if (colon) // only 1 colon return 0; colon = s + 1; } else if (*s == '.') { if (colon) // no colons before periods (probably invalid anyway) return 0; else if (bits >= 32) // only 32 bits in ipv4 return 0; else if (*(s+1) == '.') return 0; else if (*(s+1) == '\0') break; // don't add more bits to the mask for x.x., etc bits += 8; address++; } else if (*s >= '0' && *s <= '9') *address = ((*address)*10) + (*s-'0'); else return 0; // invalid character s++; } a->type = NA_IP; if (colon) a->port = atoi(colon); return bits; } // NET_StringToAdrMasked: extension to NET_StringToAdr to handle IP addresses // with masks or integers representing the bit masks qboolean NET_StringToAdrMasked (const char *s, netadr_t *a, netadr_t *amask) { char t[64]; char *spoint; int i; spoint = strchr(s, '/'); if (spoint) { // we have a slash in the address so split and resolve separately char *c; i = (int)(spoint - s) + 1; if (i > sizeof(t)) i = sizeof(t); Q_strncpyz(t, s, i); if (!ParsePartialIPv4(t, a) && !NET_StringToAdr(t, 0, a)) return false; spoint++; c = spoint; if (!*c) return false; while (*c) // check for non-numeric characters { if (*c < '0' || *c > '9') { c = NULL; break; } c++; } if (c == NULL) // we have an address so resolve it and return return ParsePartialIPv4(spoint, amask) || NET_StringToAdr(spoint, 0, amask); // otherwise generate mask for given bits i = atoi(spoint); NET_IntegerToMask(a, amask, i); } else { // we don't have a slash, resolve and fill with a full mask i = ParsePartialIPv4(s, a); if (!i && !NET_StringToAdr(s, 0, a)) return false; memset (amask, 0, sizeof(*amask)); amask->type = a->type; if (i) NET_IntegerToMask(a, amask, i); else NET_IntegerToMask(a, amask, -1); } return true; } // NET_CompareAdrMasked: given 3 addresses, 2 to compare with a complimentary mask, // returns true or false if they match qboolean NET_CompareAdrMasked(netadr_t *a, netadr_t *b, netadr_t *mask) { int i; //make sure the address being checked against matches the mask if (b->type != mask->type) return false; // check port if both are non-zero if (a->port && b->port && a->port != b->port) return false; // check to make sure all types match if (a->type != b->type) { if (a->type == NA_IP && b->type == NA_IPV6 && mask->type == NA_IP) { for (i = 0; i < 10; i++) if (b->address.ip6[i] != 0) return false; //only matches if they're 0s, otherwise its not an ipv4 address there for (; i < 12; i++) if (b->address.ip6[i] != 0xff && b->address.ip6[i] != 0x00) //0x00 is depricated return false; //only matches if they're 0s or ffs, otherwise its not an ipv4 address there for (i = 0; i < 4; i++) { if ((a->address.ip[i] & mask->address.ip[i]) != (b->address.ip6[12+i] & mask->address.ip[i])) return false; //mask doesn't match } return true; //its an ipv4 address in there, the mask matched the whole way through } if (a->type == NA_IPV6 && b->type == NA_IP && mask->type == NA_IP) { for (i = 0; i < 10; i++) if (a->address.ip6[i] != 0) return false; //only matches if they're 0s, otherwise its not an ipv4 address there for (; i < 12; i++) if (a->address.ip6[i] != 0xff && a->address.ip6[i] != 0x00) //0x00 is depricated return false; //only matches if they're 0s or ffs, otherwise its not an ipv4 address there for (i = 0; i < 4; i++) { if ((a->address.ip6[12+i] & mask->address.ip[i]) != (b->address.ip[i] & mask->address.ip[i])) return false; //mask doesn't match } return true; //its an ipv4 address in there, the mask matched the whole way through } return false; } // match on protocol type and compare address switch (a->type) { case NA_LOOPBACK: return true; case NA_BROADCAST_IP: case NA_IP: for (i = 0; i < 4; i++) { if ((a->address.ip[i] & mask->address.ip[i]) != (b->address.ip[i] & mask->address.ip[i])) return false; } break; #ifdef IPPROTO_IPV6 case NA_BROADCAST_IP6: case NA_IPV6: for (i = 0; i < 16; i++) { if ((a->address.ip6[i] & mask->address.ip6[i]) != (b->address.ip6[i] & mask->address.ip6[i])) return false; } break; #endif #ifdef USEIPX case NA_BROADCAST_IPX: case NA_IPX: for (i = 0; i < 10; i++) { if ((a->address.ipx[i] & mask->address.ipx[i]) != (b->address.ipx[i] & mask->address.ipx[i])) return false; } break; #endif #ifdef IRCCONNECT case NA_IRC: //masks are not supported, match explicitly if (strcmp(a->address.irc.user, b->address.irc.user)) return false; break; #endif default: return false; // invalid protocol } return true; // all checks passed } // UniformMaskedBits: counts number of bits in an assumed uniform mask, returns // -1 if not uniform int UniformMaskedBits(netadr_t *mask) { int bits; int b; unsigned int bs; qboolean bitenc = false; switch (mask->type) { case NA_BROADCAST_IP: case NA_IP: bits = 32; for (b = 3; b >= 0; b--) { if (mask->address.ip[b] == 0xFF) bitenc = true; else if (mask->address.ip[b]) { bs = (~mask->address.ip[b]) & 0xFF; while (bs) { if (bs & 1) { bits -= 1; if (bitenc) return -1; } else bitenc = true; bs >>= 1; } } else if (bitenc) return -1; else bits -= 8; } break; #ifdef IPPROTO_IPV6 case NA_BROADCAST_IP6: case NA_IPV6: bits = 128; for (b = 15; b >= 0; b--) { if (mask->address.ip6[b] == 0xFF) bitenc = true; else if (mask->address.ip6[b]) { bs = (~mask->address.ip6[b]) & 0xFF; while (bs) { if (bs & 1) { bits -= 1; if (bitenc) return -1; } else bitenc = true; bs >>= 1; } } else if (bitenc) return -1; else bits -= 8; } break; #endif #ifdef USEIPX case NA_BROADCAST_IPX: case NA_IPX: bits = 80; for (b = 9; b >= 0; b--) { if (mask->address.ipx[b] == 0xFF) bitenc = true; else if (mask->address.ipx[b]) { bs = (~mask->address.ipx[b]) & 0xFF; while (bs) { if (bs & 1) { bits -= 1; if (bitenc) return -1; } else bitenc = true; bs >>= 1; } } else if (bitenc) return -1; else bits -= 8; } break; #endif default: return -1; // invalid protocol } return bits; // all checks passed } char *NET_AdrToStringMasked (char *s, int len, netadr_t *a, netadr_t *amask) { int i; char adr[MAX_ADR_SIZE], mask[MAX_ADR_SIZE]; i = UniformMaskedBits(amask); if (i >= 0) snprintf(s, len, "%s/%i", NET_AdrToString(adr, sizeof(adr), a), i); else snprintf(s, len, "%s/%s", NET_AdrToString(adr, sizeof(adr), a), NET_AdrToString(mask, sizeof(mask), amask)); return s; } // Returns true if we can't bind the address locally--in other words, // the IP is NOT one of our interfaces. qboolean NET_IsClientLegal(netadr_t *adr) { #if 0 struct sockaddr_in sadr; int newsocket; if (adr->ip[0] == 127) return false; // no local connections period NetadrToSockadr (adr, &sadr); if ((newsocket = socket (PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET) Sys_Error ("NET_IsClientLegal: socket:", strerror(qerrno)); sadr.sin_port = 0; if( bind (newsocket, (void *)&sadr, sizeof(sadr)) == -1) { // It is not a local address close(newsocket); return true; } close(newsocket); return false; #else return true; #endif } qboolean NET_IsLoopBackAddress (netadr_t *adr) { // return (!strcmp(cls.servername, NET_AdrToString(net_local_adr)) || !strcmp(cls.servername, "local"); return adr->type == NA_LOOPBACK; } ///////////////////////////////////////////// //loopback stuff #if !defined(CLIENTONLY) && !defined(SERVERONLY) qboolean NET_GetLoopPacket (int sock, netadr_t *from, sizebuf_t *message) { int i; loopback_t *loop; sock &= 1; loop = &loopbacks[sock]; if (loop->send - loop->get > MAX_LOOPBACK) { extern cvar_t showdrop; if (showdrop.ival) Con_Printf("loopback dropping %i packets\n", (loop->send - MAX_LOOPBACK) - loop->get); loop->get = loop->send - MAX_LOOPBACK; } if (loop->get >= loop->send) return false; i = loop->get & (MAX_LOOPBACK-1); loop->get++; if (message->maxsize < loop->msgs[i].datalen) Sys_Error("NET_SendLoopPacket: Loopback buffer was too big"); memcpy (message->data, loop->msgs[i].data, loop->msgs[i].datalen); message->cursize = loop->msgs[i].datalen; memset (from, 0, sizeof(*from)); from->type = NA_LOOPBACK; message->packing = SZ_RAWBYTES; message->currentbit = 0; return true; } void NET_SendLoopPacket (int sock, int length, void *data, netadr_t *to) { int i; loopback_t *loop; sock &= 1; loop = &loopbacks[sock^1]; if (length > sizeof(loop->msgs[i].data)) { Con_Printf("NET_SendLoopPacket: Loopback buffer is too small"); return; } i = loop->send & (MAX_LOOPBACK-1); loop->send++; memcpy (loop->msgs[i].data, data, length); loop->msgs[i].datalen = length; } int FTENET_Loop_GetLocalAddress(ftenet_generic_connection_t *con, netadr_t *out, int adrnum) { if (adrnum==0) { out->type = NA_LOOPBACK; out->port = con->thesocket+1; } return 1; } qboolean FTENET_Loop_GetPacket(ftenet_generic_connection_t *con) { return NET_GetLoopPacket(con->thesocket, &net_from, &net_message); } qboolean FTENET_Loop_SendPacket(ftenet_generic_connection_t *con, int length, void *data, netadr_t *to) { if (to->type == NA_LOOPBACK) { NET_SendLoopPacket(con->thesocket, length, data, to); return true; } return false; } void FTENET_Loop_Close(ftenet_generic_connection_t *con) { int sock = con->thesocket; sock &= 1; loopbacks[sock].inited = false; Z_Free(con); } static ftenet_generic_connection_t *FTENET_Loop_EstablishConnection(qboolean isserver, const char *address) { ftenet_generic_connection_t *newcon; int sock; for (sock = 0; sock < 2; sock++) if (!loopbacks[sock].inited) break; if (sock == 2) return NULL; newcon = Z_Malloc(sizeof(*newcon)); if (newcon) { loopbacks[sock].inited = true; newcon->GetLocalAddress = FTENET_Loop_GetLocalAddress; newcon->GetPacket = FTENET_Loop_GetPacket; newcon->SendPacket = FTENET_Loop_SendPacket; newcon->Close = FTENET_Loop_Close; newcon->islisten = isserver; newcon->addrtype[0] = NA_LOOPBACK; newcon->addrtype[1] = NA_INVALID; newcon->thesocket = sock; } return newcon; } #endif //============================================================================= #define MAX_CONNECTIONS 8 typedef struct ftenet_connections_s { qboolean islisten; ftenet_generic_connection_t *conn[MAX_CONNECTIONS]; } ftenet_connections_t; ftenet_connections_t *FTENET_CreateCollection(qboolean listen) { ftenet_connections_t *col; col = Z_Malloc(sizeof(*col)); col->islisten = listen; return col; } static ftenet_generic_connection_t *FTENET_Loop_EstablishConnection(qboolean isserver, const char *address); static ftenet_generic_connection_t *FTENET_UDP4_EstablishConnection(qboolean isserver, const char *address); static ftenet_generic_connection_t *FTENET_UDP6_EstablishConnection(qboolean isserver, const char *address); static ftenet_generic_connection_t *FTENET_TCP4Connect_EstablishConnection(qboolean isserver, const char *address); static ftenet_generic_connection_t *FTENET_TCP6Connect_EstablishConnection(qboolean isserver, const char *address); #ifdef USEIPX static ftenet_generic_connection_t *FTENET_IPX_EstablishConnection(qboolean isserver, const char *address); #endif #ifdef HAVE_WEBSOCKCL static ftenet_generic_connection_t *FTENET_WebSocket_EstablishConnection(qboolean isserver, const char *address); #endif static ftenet_generic_connection_t *FTENET_IRCConnect_EstablishConnection(qboolean isserver, const char *address); static ftenet_generic_connection_t *FTENET_NATPMP_EstablishConnection(qboolean isserver, const char *address); #ifdef HAVE_NATPMP typedef struct { ftenet_generic_connection_t pub; ftenet_connections_t *col; netadr_t reqpmpaddr; netadr_t pmpaddr; netadr_t natadr; unsigned int refreshtime; } pmpcon_t; int FTENET_NATPMP_GetLocalAddress(struct ftenet_generic_connection_s *con, netadr_t *local, int adridx); static qboolean NET_Was_NATPMP(ftenet_connections_t *collection) { pmpcon_t *pmp; struct { qbyte ver; qbyte op; short resultcode; int age; union { struct { short privport; short pubport; int mapping_expectancy; }; qbyte ipv4[4]; }; } *pmpreqrep; int i; for (i = 0; i < MAX_CONNECTIONS; i++) { if (!collection->conn[i]) continue; if (collection->conn[i]->GetLocalAddress == FTENET_NATPMP_GetLocalAddress) { pmp = (pmpcon_t*)collection->conn[i]; if (NET_CompareAdr(&pmp->pmpaddr, &net_from)) { pmpreqrep = (void*)net_message.data; if (pmpreqrep->ver != 0) return false; if (net_message.cursize == 12 && pmpreqrep->op == 128) { char adrbuf[256]; pmp->natadr.type = NA_IP; pmp->natadr.port = 0; memcpy(pmp->natadr.address.ip, pmpreqrep->ipv4, sizeof(pmp->natadr.address.ip)); NET_AdrToString(adrbuf, sizeof(adrbuf), &pmp->natadr); // Con_Printf("Public ip is %s\n", adrbuf); return true; } if (net_message.cursize == 16 && pmpreqrep->op == 129) { switch(BigShort(pmpreqrep->resultcode)) { case 0: break; case 1: Con_Printf("NAT-PMP: unsupported version\n"); return true; case 2: Con_Printf("NAT-PMP: refused - please reconfigure router\n"); return true; case 3: Con_Printf("NAT-PMP: network failure\n"); return true; case 4: Con_Printf("NAT-PMP: out of resources\n"); return true; case 5: Con_Printf("NAT-PMP: unsupported opcode\n"); return true; default: return false; } // Con_Printf("Local port %u publically available on port %u\n", (unsigned short)BigShort(pmpreqrep->privport), (unsigned short)BigShort(pmpreqrep->pubport)); pmp->natadr.port = pmpreqrep->pubport; return true; } return false; } } } return false; } static void FTENET_NATPMP_Refresh(pmpcon_t *pmp, short oldport, ftenet_connections_t *collection) { int i; int adrno, adrcount=1; netadr_t adr; struct { qbyte ver; qbyte op; short reserved1; short privport; short pubport; int mapping_expectancy; } pmpreqmsg; pmpreqmsg.ver = 0; pmpreqmsg.op = 1; pmpreqmsg.reserved1 = BigShort(0); pmpreqmsg.privport = BigShort(0); pmpreqmsg.pubport = BigShort(0); pmpreqmsg.mapping_expectancy = BigLong(60*5); if (!collection) return; for (i = 0; i < MAX_CONNECTIONS; i++) { if (!collection->conn[i]) continue; if (collection->conn[i]->GetLocalAddress && collection->conn[i]->GetLocalAddress != FTENET_NATPMP_GetLocalAddress) { for (adrno = 0, adrcount=1; (adrcount = collection->conn[i]->GetLocalAddress(collection->conn[i], &adr, adrno)) && adrno < adrcount; adrno++) { // Con_Printf("net address (%s): %s\n", collection->conn[i]->name, NET_AdrToString(adrbuf, sizeof(adrbuf), adr)); //unipv6ify it if its a hybrid socket. if (adr.type == NA_IPV6 && !*(int*)&adr.address.ip6[0] && !*(int*)&adr.address.ip6[4] && !*(short*)&adr.address.ip6[8] && *(short*)&adr.address.ip6[10]==(short)0xffff && !*(int*)&adr.address.ip6[12]) { *(int*)adr.address.ip = *(int*)&adr.address.ip6[12]; adr.type = NA_IP; } if (adr.type == NA_IP) { if (adr.address.ip[0] == 127) //yes. loopback has a lot of ip addresses. wasteful but whatever. continue; //assume a netmask of 255.255.255.0 adr.address.ip[3] = 1; } // else if (adr.type == NA_IPV6) // { // } else continue; pmpreqmsg.privport = adr.port; pmpreqmsg.pubport = oldport?oldport:adr.port; if (*(int*)pmp->reqpmpaddr.address.ip == INADDR_ANY) { pmp->pmpaddr = adr; pmp->pmpaddr.port = pmp->reqpmpaddr.port; } else pmp->pmpaddr = pmp->reqpmpaddr; if (*(int*)pmp->pmpaddr.address.ip == INADDR_ANY) continue; //get the public ip. pmpreqmsg.op = 0; NET_SendPacket(NS_SERVER, 2, &pmpreqmsg, &pmp->pmpaddr); //open the firewall/nat. pmpreqmsg.op = 1; NET_SendPacket(NS_SERVER, sizeof(pmpreqmsg), &pmpreqmsg, &pmp->pmpaddr); break; } } } } #define PMP_POLL_TIME (1000*30)//every 30 seconds int FTENET_NATPMP_GetLocalAddress(struct ftenet_generic_connection_s *con, netadr_t *local, int adridx) { pmpcon_t *pmp = (pmpcon_t*)con; local->type = NA_INVALID; if (adridx == 0) *local = pmp->natadr; return (pmp->natadr.type != NA_INVALID) && (pmp->natadr.port != 0); } qboolean FTENET_NATPMP_GetPacket(struct ftenet_generic_connection_s *con) { pmpcon_t *pmp = (pmpcon_t*)con; unsigned int now = Sys_Milliseconds(); if (now - pmp->refreshtime > PMP_POLL_TIME) //weird logic to cope with wrapping { pmp->refreshtime = now; FTENET_NATPMP_Refresh(pmp, pmp->natadr.port, pmp->col); } return false; } qboolean FTENET_NATPMP_SendPacket(struct ftenet_generic_connection_s *con, int length, void *data, netadr_t *to) { return false; } void FTENET_NATPMP_Close(struct ftenet_generic_connection_s *con) { //FIXME: we should send a packet to close the port Z_Free(con); } ftenet_generic_connection_t *FTENET_NATPMP_EstablishConnection(qboolean isserver, const char *address) { pmpcon_t *pmp; netadr_t pmpadr; NET_PortToAdr(NA_IP, address, &pmpadr); if (pmpadr.type == NA_NATPMP) pmpadr.type = NA_IP; if (pmpadr.type != NA_IP) return NULL; pmp = Z_Malloc(sizeof(*pmp)); pmp->col = svs.sockets; Q_strncpyz(pmp->pub.name, "natpmp", sizeof(pmp->pub.name)); pmp->reqpmpaddr = pmpadr; pmp->pub.GetLocalAddress = FTENET_NATPMP_GetLocalAddress; pmp->pub.GetPacket = FTENET_NATPMP_GetPacket; //qboolean (*ChangeLocalAddress)(struct ftenet_generic_connection_s *con, const char *newaddress); pmp->pub.SendPacket = FTENET_NATPMP_SendPacket; pmp->pub.Close = FTENET_NATPMP_Close; pmp->pub.thesocket = INVALID_SOCKET; pmp->refreshtime = Sys_Milliseconds() + PMP_POLL_TIME*64; return &pmp->pub; } #endif qboolean FTENET_AddToCollection(ftenet_connections_t *col, const char *name, const char *address, netadrtype_t addrtype, qboolean islisten) { int count = 0; int i; netadr_t adr; ftenet_generic_connection_t *(*establish)(qboolean isserver, const char *address) = NULL; if (!col) return false; if (!address || !*address) adr.type = NA_INVALID; else if (islisten) NET_PortToAdr(addrtype, address, &adr); else NET_StringToAdr(address, 0, &adr); switch(adr.type) { default: establish = NULL; break; #ifdef HAVE_NATPMP case NA_NATPMP: establish = FTENET_NATPMP_EstablishConnection; break; #endif #if !defined(CLIENTONLY) && !defined(SERVERONLY) case NA_LOOPBACK: establish = FTENET_Loop_EstablishConnection; break; #endif #ifdef HAVE_IPV4 case NA_IP: establish = FTENET_UDP4_EstablishConnection; break; #endif #ifdef IPPROTO_IPV6 case NA_IPV6: establish = FTENET_UDP6_EstablishConnection; break; #endif #ifdef USEIPX case NA_IPX: establish = FTENET_IPX_EstablishConnection; break; #endif case NA_WEBSOCKET: #ifdef HAVE_WEBSOCKCL if (!islisten) establish = FTENET_WebSocket_EstablishConnection; #endif #ifdef TCPCONNECT establish = FTENET_TCP4Connect_EstablishConnection; #endif break; #ifdef IRCCONNECT case NA_IRC: establish = FTENET_IRCConnect_EstablishConnection; break; #endif #ifdef TCPCONNECT case NA_TCP: establish = FTENET_TCP4Connect_EstablishConnection; break; #endif #if defined(TCPCONNECT) && defined(IPPROTO_IPV6) case NA_TCPV6: establish = FTENET_TCP6Connect_EstablishConnection; break; #endif } if (name) { for (i = 0; i < MAX_CONNECTIONS; i++) { if (col->conn[i]) if (col->conn[i]->name && !strcmp(col->conn[i]->name, name)) { if (address && *address) if (col->conn[i]->ChangeLocalAddress) { if (col->conn[i]->ChangeLocalAddress(col->conn[i], address)) return true; } col->conn[i]->Close(col->conn[i]); col->conn[i] = NULL; } } } if (address && *address && establish) { for (i = 0; i < MAX_CONNECTIONS; i++) { if (!col->conn[i]) { address = COM_Parse(address); col->conn[i] = establish(islisten, com_token); if (!col->conn[i]) break; if (name) Q_strncpyz(col->conn[i]->name, name, sizeof(col->conn[i]->name)); count++; if (address && *address) continue; break; } } } return count > 0; } void FTENET_CloseCollection(ftenet_connections_t *col) { int i; if (!col) return; for (i = 0; i < MAX_CONNECTIONS; i++) { if (col->conn[i]) { col->conn[i]->Close(col->conn[i]); } } Z_Free(col); } void FTENET_Generic_Close(ftenet_generic_connection_t *con) { #ifdef HAVE_PACKET if (con->thesocket != INVALID_SOCKET) closesocket(con->thesocket); #endif Z_Free(con); } int FTENET_Generic_GetLocalAddress(ftenet_generic_connection_t *con, netadr_t *out, int count) { #ifndef HAVE_PACKET return 0; #else struct sockaddr_qstorage from; int fromsize = sizeof(from); netadr_t adr; #ifdef USE_GETHOSTNAME_LOCALLISTING char adrs[MAX_ADR_SIZE]; int b; #endif int idx = 0; if (getsockname (con->thesocket, (struct sockaddr*)&from, &fromsize) != -1) { memset(&adr, 0, sizeof(adr)); SockadrToNetadr(&from, &adr); #ifdef USE_GETHOSTNAME_LOCALLISTING if (adr.type == NA_IPV6 && !*(int*)&adr.address.ip6[0] && !*(int*)&adr.address.ip6[4] && !*(short*)&adr.address.ip6[8] && *(short*)&adr.address.ip6[10]==(short)0xffff && !*(int*)&adr.address.ip6[12]) { /*ipv4-mapped address ANY, pretend we read blank*/ b = sizeof(adr.address); } else { for (b = 0; b < sizeof(adr.address); b++) if (((unsigned char*)&adr.address)[b] != 0) break; } if (b == sizeof(adr.address)) { gethostname(adrs, sizeof(adrs)); #ifdef IPPROTO_IPV6 if (pgetaddrinfo) { struct addrinfo hints, *result, *itr; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = 0; /* Allow IPv4 or IPv6 */ hints.ai_socktype = SOCK_DGRAM; /* Datagram socket */ hints.ai_flags = 0; hints.ai_protocol = 0; /* Any protocol */ if (pgetaddrinfo(adrs, NULL, &hints, &result) != 0) { if (idx++ == count) *out = adr; } else { for (itr = result; itr; itr = itr->ai_next) { if (itr->ai_addr->sa_family != ((struct sockaddr_in*)&from)->sin_family) { #ifdef IPV6_V6ONLY if (((struct sockaddr_in*)&from)->sin_family == AF_INET6 && itr->ai_addr->sa_family == AF_INET) { int ipv6only = true; int optlen = sizeof(ipv6only); getsockopt(con->thesocket, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&ipv6only, &optlen); if (ipv6only) continue; } else #endif continue; } if (itr->ai_addr->sa_family == AF_INET || itr->ai_addr->sa_family == AF_INET6 #ifdef USEIPX || itr->ai_addr->sa_family == AF_IPX #endif ) if (idx++ == count) { SockadrToNetadr((struct sockaddr_qstorage*)itr->ai_addr, out); out->port = ((struct sockaddr_in*)&from)->sin_port; } } pfreeaddrinfo(result); /*if none found, fill in the 0.0.0.0 or whatever*/ if (!idx) { idx++; *out = adr; } } } else #endif { struct hostent *h; h = gethostbyname(adrs); b = 0; #ifdef HAVE_IPV4 if(h && h->h_addrtype == AF_INET) { for (b = 0; h->h_addr_list[b]; b++) { ((struct sockaddr_in*)&from)->sin_family = AF_INET; memcpy(&((struct sockaddr_in*)&from)->sin_addr, h->h_addr_list[b], sizeof(((struct sockaddr_in*)&from)->sin_addr)); SockadrToNetadr(&from, &adr); if (idx++ == count) *out = adr; } } #endif #ifdef IPPROTO_IPV6 if(h && h->h_addrtype == AF_INET6) { for (b = 0; h->h_addr_list[b]; b++) { ((struct sockaddr_in*)&from)->sin_family = AF_INET6; memcpy(&((struct sockaddr_in6*)&from)->sin6_addr, h->h_addr_list[b], sizeof(((struct sockaddr_in6*)&from)->sin6_addr)); SockadrToNetadr(&from, &adr); if (idx++ == count) *out = adr; } } #endif if (b == 0) { if (idx++ == count) *out = adr; } } } else #endif { if (adr.type == NA_IPV6 && !*(int*)&adr.address.ip6[0] && !*(int*)&adr.address.ip6[4] && !*(int*)&adr.address.ip6[8] && !*(int*)&adr.address.ip6[12]) { if (idx++ == count) { *out = adr; out->type = NA_IP; } } if (idx++ == count) *out = adr; } } return idx; #endif } qboolean FTENET_Generic_GetPacket(ftenet_generic_connection_t *con) { #ifndef HAVE_PACKET return false; #else struct sockaddr_qstorage from; int fromlen; int ret; int err; char adr[MAX_ADR_SIZE]; if (con->thesocket == INVALID_SOCKET) return false; fromlen = sizeof(from); ret = recvfrom (con->thesocket, (char *)net_message_buffer, sizeof(net_message_buffer), 0, (struct sockaddr*)&from, &fromlen); if (ret == -1) { err = qerrno; if (err == EWOULDBLOCK) return false; if (err == EMSGSIZE) { SockadrToNetadr (&from, &net_from); Con_TPrintf (TL_OVERSIZEPACKETFROM, NET_AdrToString (adr, sizeof(adr), &net_from)); return false; } if (err == ECONNABORTED || err == ECONNRESET) { Con_TPrintf (TL_CONNECTIONLOSTORABORTED); //server died/connection lost. #ifndef SERVERONLY if (cls.state != ca_disconnected && !con->islisten) { if (cls.lastarbiatarypackettime+5 < Sys_DoubleTime()) //too many mvdsv Cbuf_AddText("disconnect\nreconnect\n", RESTRICT_LOCAL); //retry connecting. else Con_Printf("Packet was not delivered - server might be badly configured\n"); return false; } #endif return false; } Con_Printf ("NET_GetPacket: Error (%i): %s\n", err, strerror(err)); return false; } SockadrToNetadr (&from, &net_from); net_message.packing = SZ_RAWBYTES; net_message.currentbit = 0; net_message.cursize = ret; if (net_message.cursize == sizeof(net_message_buffer) ) { Con_TPrintf (TL_OVERSIZEPACKETFROM, NET_AdrToString (adr, sizeof(adr), &net_from)); return false; } return true; #endif } qboolean FTENET_Generic_SendPacket(ftenet_generic_connection_t *con, int length, void *data, netadr_t *to) { #ifndef HAVE_PACKET return false; #else struct sockaddr_qstorage addr; int size; int ret; for (size = 0; size < FTENET_ADDRTYPES; size++) if (to->type == con->addrtype[size]) break; if (size == FTENET_ADDRTYPES) return false; #ifdef IPPROTO_IPV6 /*special code to handle sending to hybrid sockets*/ if (con->addrtype[1] == NA_IPV6 && to->type == NA_IP) { memset(&addr, 0, sizeof(struct sockaddr_in6)); ((struct sockaddr_in6*)&addr)->sin6_family = AF_INET6; *(short*)&((struct sockaddr_in6*)&addr)->sin6_addr.s6_addr[10] = 0xffff; *(int*)&((struct sockaddr_in6*)&addr)->sin6_addr.s6_addr[12] = *(int*)&to->address.ip; ((struct sockaddr_in6*)&addr)->sin6_port = to->port; size = sizeof(struct sockaddr_in6); } else #endif { NetadrToSockadr (to, &addr); switch(to->type) { default: Con_Printf("Bad address type\n"); break; #ifdef USEIPX //who uses ipx nowadays anyway? case NA_BROADCAST_IPX: case NA_IPX: size = sizeof(struct sockaddr_ipx); break; #endif case NA_BROADCAST_IP: case NA_IP: size = sizeof(struct sockaddr_in); break; #ifdef IPPROTO_IPV6 case NA_BROADCAST_IP6: case NA_IPV6: size = sizeof(struct sockaddr_in6); break; #endif } } ret = sendto (con->thesocket, data, length, 0, (struct sockaddr*)&addr, size ); if (ret == -1) { int ecode = qerrno; // wouldblock is silent if (ecode == EWOULDBLOCK) return true; if (ecode == ECONNREFUSED) return true; if (ecode == EACCES) { Con_Printf("Access denied: check firewall\n"); return true; } #ifndef SERVERONLY if (ecode == EADDRNOTAVAIL) Con_DPrintf("NET_SendPacket Warning: %i\n", ecode); else #endif Con_TPrintf (TL_NETSENDERROR, ecode); } return true; #endif } qboolean NET_PortToAdr (int adrfamily, const char *s, netadr_t *a) { char *e; int port; port = strtoul(s, &e, 10); if (*e) //if *e then its not just a single number in there, so treat it as a proper address. return NET_StringToAdr(s, 0, a); else if (port) { memset(a, 0, sizeof(*a)); a->port = htons((unsigned short)port); a->type = adrfamily; return a->type != NA_INVALID; } a->type = NA_INVALID; return false; } ftenet_generic_connection_t *FTENET_Generic_EstablishConnection(int adrfamily, int protocol, qboolean isserver, const char *address) { #ifndef HAVE_PACKET return NULL; #else //this is written to support either ipv4 or ipv6, depending on the remote addr. ftenet_generic_connection_t *newcon; unsigned long _true = true; SOCKET newsocket = INVALID_SOCKET; int temp; netadr_t adr; struct sockaddr_qstorage qs; int family; int port; int bindtries; int bufsz; qboolean hybrid = false; if (!NET_PortToAdr(adrfamily, address, &adr)) { Con_Printf("unable to resolve local address %s\n", address); return NULL; //couldn't resolve the name } temp = NetadrToSockadr(&adr, &qs); family = ((struct sockaddr*)&qs)->sa_family; #if defined(IPPROTO_IPV6) && defined(IPV6_V6ONLY) if (isserver && family == AF_INET && net_hybriddualstack.ival && !((struct sockaddr_in*)&qs)->sin_addr.s_addr) { unsigned long _false = false; if ((newsocket = socket (AF_INET6, SOCK_DGRAM, protocol)) != INVALID_SOCKET) { if (0 == setsockopt(newsocket, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&_false, sizeof(_false))) { // int ip = ((struct sockaddr_in*)&qs)->sin_addr.s_addr; int port = ((struct sockaddr_in*)&qs)->sin_port; // ip = ((struct sockaddr_in*)&qs)->sin_addr.s_addr; memset(&qs, 0, sizeof(struct sockaddr_in6)); ((struct sockaddr_in6*)&qs)->sin6_family = AF_INET6; /* if (((struct sockaddr_in*)&qs)->sin_addr.s_addr) { ((struct sockaddr_in6*)&qs)->sin6_addr.s6_addr[10] = 0xff; ((struct sockaddr_in6*)&qs)->sin6_addr.s6_addr[11] = 0xff; ((struct sockaddr_in6*)&qs)->sin6_addr.s6_addr[12] = ((qbyte*)&ip)[0]; ((struct sockaddr_in6*)&qs)->sin6_addr.s6_addr[13] = ((qbyte*)&ip)[1]; ((struct sockaddr_in6*)&qs)->sin6_addr.s6_addr[14] = ((qbyte*)&ip)[2]; ((struct sockaddr_in6*)&qs)->sin6_addr.s6_addr[15] = ((qbyte*)&ip)[3]; } */ ((struct sockaddr_in6*)&qs)->sin6_port = port; temp = sizeof(struct sockaddr_in6); hybrid = true; } else { /*v6only failed... if the option doesn't exist, chances are this is a hybrid system which doesn't support both simultaneously anyway*/ closesocket(newsocket); newsocket = INVALID_SOCKET; } } } #endif if (newsocket == INVALID_SOCKET) if ((newsocket = socket (family, SOCK_DGRAM, protocol)) == INVALID_SOCKET) { return NULL; } #if defined(IPPROTO_IPV6) && defined(IPV6_V6ONLY) if (family == AF_INET6) setsockopt(newsocket, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&_true, sizeof(_true)); #endif #ifdef _WIN32 //win32 is so fucked up setsockopt(newsocket, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (char *)&_true, sizeof(_true)); #endif bufsz = 1<<18; setsockopt(newsocket, SOL_SOCKET, SO_RCVBUF, (void*)&bufsz, sizeof(bufsz)); //try and find an unused port. port = ntohs(((struct sockaddr_in*)&qs)->sin_port); for (bindtries = 100; bindtries > 0; bindtries--) { ((struct sockaddr_in*)&qs)->sin_port = htons((unsigned short)(port+100-bindtries)); if ((bind(newsocket, (struct sockaddr *)&qs, temp) == INVALID_SOCKET)) { continue; } break; } if (!bindtries) { SockadrToNetadr(&qs, &adr); //mneh, reuse qs. NET_AdrToString((char*)&qs, sizeof(qs), &adr); Con_Printf("Unable to listen at %s\n", (char*)&qs); closesocket(newsocket); return NULL; } if (ioctlsocket (newsocket, FIONBIO, &_true) == -1) Sys_Error ("UDP_OpenSocket: ioctl FIONBIO: %s", strerror(qerrno)); // // determine my name & address if we don't already know it // if (!net_local_cl_ipadr.type == NA_INVALID) NET_GetLocalAddress (newsocket, &net_local_cl_ipadr); newcon = Z_Malloc(sizeof(*newcon)); if (newcon) { newcon->GetLocalAddress = FTENET_Generic_GetLocalAddress; newcon->GetPacket = FTENET_Generic_GetPacket; newcon->SendPacket = FTENET_Generic_SendPacket; newcon->Close = FTENET_Generic_Close; newcon->islisten = isserver; if (hybrid) { newcon->addrtype[0] = NA_IP; newcon->addrtype[1] = NA_IPV6; } else { newcon->addrtype[0] = adr.type; newcon->addrtype[1] = NA_INVALID; } newcon->thesocket = newsocket; return newcon; } else { closesocket(newsocket); return NULL; } #endif } #ifdef IPPROTO_IPV6 ftenet_generic_connection_t *FTENET_UDP6_EstablishConnection(qboolean isserver, const char *address) { return FTENET_Generic_EstablishConnection(NA_IPV6, IPPROTO_UDP, isserver, address); } #endif #ifdef HAVE_IPV4 ftenet_generic_connection_t *FTENET_UDP4_EstablishConnection(qboolean isserver, const char *address) { return FTENET_Generic_EstablishConnection(NA_IP, IPPROTO_UDP, isserver, address); } #endif #ifdef USEIPX ftenet_generic_connection_t *FTENET_IPX_EstablishConnection(qboolean isserver, const char *address) { return FTENET_Generic_EstablishConnection(NA_IPX, NSPROTO_IPX, isserver, address); } #endif #ifdef TCPCONNECT typedef struct ftenet_tcpconnect_stream_s { SOCKET socketnum; int inlen; int outlen; enum { TCPC_UNKNOWN, //waiting to see what they send us. TCPC_UNFRAMED, //something else is doing the framing (ie: we're running in emscripten and over some hidden websocket connection) TCPC_HTTPCLIENT, //we're sending a file to this victim. TCPC_QIZMO, //'qizmo\n' handshake, followed by packets prefixed with a 16bit packet length. TCPC_WEBSOCKETU, //utf-8 encoded data. TCPC_WEBSOCKETB, //binary encoded data (subprotocol = 'binary') } clienttype; char inbuffer[3000]; char outbuffer[3000]; vfsfile_t *file; float timeouttime; netadr_t remoteaddr; struct ftenet_tcpconnect_stream_s *next; } ftenet_tcpconnect_stream_t; typedef struct { ftenet_generic_connection_t generic; int active; ftenet_tcpconnect_stream_t *tcpstreams; } ftenet_tcpconnect_connection_t; void tobase64(unsigned char *out, int outlen, unsigned char *in, int inlen) { static unsigned char tab[64] = { 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P', 'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f', 'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v', 'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/' }; unsigned int usedbits = 0; unsigned int val = 0; outlen--; while(inlen) { while(usedbits < 24 && inlen) { val <<= 8; val |= (*in++); inlen--; usedbits += 8; } if (outlen < 4) return; val <<= 24 - usedbits; *out++ = (usedbits > 0)?tab[(val>>18)&0x3f]:'='; *out++ = (usedbits > 6)?tab[(val>>12)&0x3f]:'='; *out++ = (usedbits > 12)?tab[(val>>6)&0x3f]:'='; *out++ = (usedbits > 18)?tab[(val>>0)&0x3f]:'='; val=0; usedbits = 0; } *out = 0; } #include "fs.h" qboolean FTENET_TCPConnect_GetPacket(ftenet_generic_connection_t *gcon) { ftenet_tcpconnect_connection_t *con = (ftenet_tcpconnect_connection_t*)gcon; int ret; int err; char adr[MAX_ADR_SIZE]; struct sockaddr_qstorage from; int fromlen; float timeval = Sys_DoubleTime(); ftenet_tcpconnect_stream_t *st; st = con->tcpstreams; //remove any stale ones while (con->tcpstreams && con->tcpstreams->socketnum == INVALID_SOCKET) { st = con->tcpstreams; con->tcpstreams = con->tcpstreams->next; BZ_Free(st); } for (st = con->tcpstreams; st; st = st->next) {//client receiving only via tcp while (st->next && st->next->socketnum == INVALID_SOCKET) { ftenet_tcpconnect_stream_t *temp; temp = st->next; st->next = st->next->next; BZ_Free(temp); con->active--; } //due to the above checks about invalid sockets, the socket is always open for st below. if (st->timeouttime < timeval) { Con_Printf ("tcp peer %s timed out\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); goto closesvstream; } ret = recv(st->socketnum, st->inbuffer+st->inlen, sizeof(st->inbuffer)-st->inlen, 0); if (ret == 0) { Con_Printf ("tcp peer %s closed connection\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); goto closesvstream; } else if (ret == -1) { err = qerrno; if (err == EWOULDBLOCK) ret = 0; else { if (err == ECONNABORTED || err == ECONNRESET) { Con_TPrintf (TL_CONNECTIONLOSTORABORTED); //server died/connection lost. } else Con_Printf ("TCPConnect_GetPacket: Error (%i): %s\n", err, strerror(err)); closesvstream: closesocket(st->socketnum); st->socketnum = INVALID_SOCKET; continue; } } st->inlen += ret; switch(st->clienttype) { case TCPC_UNKNOWN: if (st->inlen < 6) continue; if (!strncmp(st->inbuffer, "qizmo\n", 6)) { memmove(st->inbuffer, st->inbuffer+6, st->inlen - (6)); st->inlen -= 6; st->clienttype = TCPC_QIZMO; if (con->generic.islisten) { //send the qizmo handshake response. send(st->socketnum, "qizmo\n", 6, 0); } } else if (con->generic.islisten && !strncmp(st->inbuffer, "GET ", 4)) { int i, j; int attr = 0; int alen = 0; qboolean headerscomplete = false; enum { WCATTR_METHOD, WCATTR_URL, WCATTR_HTTP, WCATTR_HOST, WCATTR_UPGRADE, WCATTR_CONNECTION, WCATTR_WSKEY, WCATTR_WSVER, //WCATTR_ORIGIN, WCATTR_WSPROTO, //WCATTR_WSEXT, WCATTR_COUNT }; char arg[WCATTR_COUNT][64]; for (i = 0; i < WCATTR_COUNT; i++) arg[i][0] = 0; for (i = 0; i < st->inlen; i++) { if (alen == 63) goto handshakeerror; if (st->inbuffer[i] == ' ' || st->inbuffer[i] == '\t') { arg[attr][alen++] = 0; alen=0; if (attr++ == WCATTR_HTTP) break; for (; i < st->inlen && (st->inbuffer[i] == ' ' || st->inbuffer[i] == '\t'); i++) ; if (i == st->inlen) break; } arg[attr][alen++] = st->inbuffer[i]; if (st->inbuffer[i] == '\n') { arg[attr][alen++] = 0; alen=0; break; } } i++; attr = 0; j = i; for (; i < st->inlen; i++) { if ((i+1 < st->inlen && st->inbuffer[i] == '\r' && st->inbuffer[i+1] == '\n') || (i < st->inlen && st->inbuffer[i] == '\n')) { i+=2; headerscomplete = true; break; } for (; i < st->inlen && (st->inbuffer[i] == ' ' || st->inbuffer[i] == '\t'); i++) ; if (i == st->inlen) break; for (j = i; j < st->inlen; j++) { if (st->inbuffer[j] == ':' || st->inbuffer[j] == '\n') { /*set j to the end of the word, going back past whitespace*/ while (j > i && (st->inbuffer[j-1] == ' ' || st->inbuffer[i-1] == '\t')) j--; break; } } if (!strnicmp(&st->inbuffer[i], "Host", j-i)) attr = WCATTR_HOST; else if (!strnicmp(&st->inbuffer[i], "Upgrade", j-i)) attr = WCATTR_UPGRADE; else if (!strnicmp(&st->inbuffer[i], "Connection", j-i)) attr = WCATTR_CONNECTION; else if (!strnicmp(&st->inbuffer[i], "Sec-WebSocket-Key", j-i)) attr = WCATTR_WSKEY; else if (!strnicmp(&st->inbuffer[i], "Sec-WebSocket-Version", j-i)) attr = WCATTR_WSVER; // else if (!strnicmp(&st->inbuffer[i], "Origin", j-i)) // attr = WCATTR_ORIGIN; else if (!strnicmp(&st->inbuffer[i], "Sec-WebSocket-Protocol", j-i)) attr = WCATTR_WSPROTO; // else if (!strnicmp(&st->inbuffer[i], "Sec-WebSocket-Extensions", j-i)) // attr = WCATTR_WSEXT; else attr = 0; i = j; /*skip over the whitespace at the end*/ for (; i < st->inlen && (st->inbuffer[i] == ' ' || st->inbuffer[i] == '\t'); i++) ; if (i < st->inlen && st->inbuffer[i] == ':') { i++; for (; i < st->inlen && (st->inbuffer[i] == ' ' || st->inbuffer[i] == '\t'); i++) ; j = i; for (; i < st->inlen && st->inbuffer[i] != '\n'; i++) ; if (i > j && st->inbuffer[i-1] == '\r') i--; if (attr) Q_strncpyz(arg[attr], &st->inbuffer[j], (i-j > 63)?64:(i - j + 1)); if (i < st->inlen && st->inbuffer[i] == '\r') i++; } else { /*just a word on the line on its own*/ goto handshakeerror; } } if (headerscomplete) { char *resp; //must be a Host, Upgrade=websocket, Connection=Upgrade, Sec-WebSocket-Key=base64(randbytes(16)), Sec-WebSocket-Version=13 //optionally will be Origin=url, Sec-WebSocket-Protocol=FTEWebSocket, Sec-WebSocket-Extensions //other fields will be ignored. if (!stricmp(arg[WCATTR_UPGRADE], "websocket") && (!stricmp(arg[WCATTR_CONNECTION], "Upgrade") || !stricmp(arg[WCATTR_CONNECTION], "keep-alive, Upgrade"))) { if (atoi(arg[WCATTR_WSVER]) != 13) { Con_Printf("Outdated websocket request for \"%s\" from \"%s\". got version %i, expected version 13\n", arg[WCATTR_URL], NET_AdrToString (adr, sizeof(adr), &st->remoteaddr), atoi(arg[WCATTR_WSVER])); memmove(st->inbuffer, st->inbuffer+i, st->inlen - (i)); st->inlen -= i; resp = va( "HTTP/1.1 426 Upgrade Required\r\n" "Sec-WebSocket-Version: 13\r\n" "\r\n"); //send the websocket handshake rejection. send(st->socketnum, resp, strlen(resp), 0); goto closesvstream; } else { char acceptkey[20*2]; unsigned char sha1digest[20]; char *blurgh; memmove(st->inbuffer, st->inbuffer+i, st->inlen - (i)); st->inlen -= i; blurgh = va("%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11", arg[WCATTR_WSKEY]); tobase64(acceptkey, sizeof(acceptkey), sha1digest, SHA1(sha1digest, sizeof(sha1digest), blurgh, strlen(blurgh))); Con_Printf("Websocket request for %s from %s\n", arg[WCATTR_URL], NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); resp = va( "HTTP/1.1 101 WebSocket Protocol Handshak\r\n" "Upgrade: websocket\r\n" "Connection: Upgrade\r\n" "Access-Control-Allow-Origin: *\r\n" //allow cross-origin requests. this means you can use any domain to play on any public server. "Sec-WebSocket-Accept: %s\r\n" // "Sec-WebSocket-Protocol: FTEWebSocket\r\n" "\r\n", acceptkey); //send the websocket handshake response. send(st->socketnum, resp, strlen(resp), 0); //and the connection is okay if (!strcmp(arg[WCATTR_WSPROTO], "binary")) st->clienttype = TCPC_WEBSOCKETB; //emscripten doesn't give us a choice, but its compact. else st->clienttype = TCPC_WEBSOCKETU; //nacl supports only utf-8 encoded data, at least at the time I implemented it. } } else { memmove(st->inbuffer, st->inbuffer+i, st->inlen - (i)); st->inlen -= i; if (!strcmp(arg[WCATTR_URL], "/live.html")) { resp = va( "HTTP/1.1 200 Ok\r\n" "Connection: Close\r\n" "Content-Type: text/html\r\n" "\r\n" "" "" "" "
" "" "" "" "" "Please install a plugin first.
" "
" "
" "
" "" ); } /* else if ((!strcmp(arg[WCATTR_URL], "/ftewebgl.html") || !strcmp(arg[WCATTR_URL], "/ftewebgl.html.fmf") || !strcmp(arg[WCATTR_URL], "/pak0.pak")) && ((st->file = VFSOS_Open(va("C:/Incoming/vm%s", arg[WCATTR_URL]), "rb")))) { Con_Printf("Downloading %s to %s\n", arg[WCATTR_URL], NET_AdrToString (adr, sizeof(adr), st->remoteaddr)); resp = va( "HTTP/1.1 200 Ok\r\n" "Content-Type: text/html\r\n" "Content-Length: %i\r\n" "\r\n", VFS_GETLEN(st->file) ); send(st->socketnum, resp, strlen(resp), 0); st->clienttype = TCPC_HTTPCLIENT; continue; } */ else { Con_Printf("Invalid download request %s to %s\n", arg[WCATTR_URL], NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); resp = va( "HTTP/1.1 404 Ok\r\n" "Connection: Close\r\n" "Content-Type: text/html\r\n" "\r\n" "This is a Quake WebSocket server, not an http server.
\r\n" ""FULLENGINENAME"" ); } //send the websocket handshake rejection. send(st->socketnum, resp, strlen(resp), 0); goto closesvstream; } } } else { handshakeerror: Con_Printf ("Unknown TCP handshake from %s\n", NET_AdrToString (adr, sizeof(adr), &net_from)); goto closesvstream; } break; case TCPC_HTTPCLIENT: if (st->outlen) { /*try and flush the old data*/ int done; done = send(st->socketnum, st->outbuffer, st->outlen, 0); if (done > 0) { memmove(st->outbuffer, st->outbuffer + done, st->outlen - done); st->outlen -= done; st->timeouttime = timeval + 30; } } if (!st->outlen) { st->outlen = VFS_READ(st->file, st->outbuffer, sizeof(st->outbuffer)); if (st->outlen <= 0) { VFS_CLOSE(st->file); st->file = NULL; st->clienttype = TCPC_UNKNOWN; Con_Printf ("Outgoing file transfer complete\n"); } } continue; case TCPC_QIZMO: if (st->inlen < 2) continue; net_message.cursize = BigShort(*(short*)st->inbuffer); if (net_message.cursize >= sizeof(net_message_buffer) ) { Con_TPrintf (TL_OVERSIZEPACKETFROM, NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); goto closesvstream; } if (net_message.cursize+2 > st->inlen) { //not enough buffered to read a packet out of it. continue; } memcpy(net_message_buffer, st->inbuffer+2, net_message.cursize); memmove(st->inbuffer, st->inbuffer+net_message.cursize+2, st->inlen - (net_message.cursize+2)); st->inlen -= net_message.cursize+2; net_message.packing = SZ_RAWBYTES; net_message.currentbit = 0; net_from = st->remoteaddr; return true; case TCPC_UNFRAMED: if (!st->inlen) continue; net_message.cursize = st->inlen; memcpy(net_message_buffer, st->inbuffer, net_message.cursize); st->inlen = 0; net_message.packing = SZ_RAWBYTES; net_message.currentbit = 0; net_from = st->remoteaddr; return true; case TCPC_WEBSOCKETU: case TCPC_WEBSOCKETB: while (st->inlen >= 2) { unsigned short ctrl = ((unsigned char*)st->inbuffer)[0]<<8 | ((unsigned char*)st->inbuffer)[1]; unsigned long paylen; unsigned int payoffs = 2; unsigned int mask = 0; st->inbuffer[st->inlen]=0; if ((ctrl & 0x7f) == 127) { //as a payload is not allowed to be encoded as too large a type, and quakeworld never used packets larger than 1450 bytes anyway, this code isn't needed (65k is the max even without this) // if (sizeof(paylen) < 8) { Con_Printf ("%s: payload frame too large\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); goto closesvstream; } /* else { if (payoffs + 8 > st->inlen) break; paylen = ((unsigned char*)st->inbuffer)[payoffs+0]<<56 | ((unsigned char*)st->inbuffer)[payoffs+1]<<48 | ((unsigned char*)st->inbuffer)[payoffs+2]<<40 | ((unsigned char*)st->inbuffer)[payoffs+3]<<32 | ((unsigned char*)st->inbuffer)[payoffs+4]<<24 | ((unsigned char*)st->inbuffer)[payoffs+5]<<16 | ((unsigned char*)st->inbuffer)[payoffs+6]<<8 | ((unsigned char*)st->inbuffer)[payoffs+7]<<0; if (paylen < 0x10000) { Con_Printf ("%s: payload size encoded badly\n", NET_AdrToString (st->remoteaddr, sizeof(st->remoteaddr), net_from)); goto closesvstream; } payoffs += 8; } */ } else if ((ctrl & 0x7f) == 126) { if (payoffs + 2 > st->inlen) break; paylen = ((unsigned char*)st->inbuffer)[payoffs+0]<<8 | ((unsigned char*)st->inbuffer)[payoffs+1]<<0; if (paylen < 126) { Con_Printf ("%s: payload size encoded badly\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); goto closesvstream; } payoffs += 2; } else { paylen = ctrl & 0x7f; } if (ctrl & 0x80) { if (payoffs + 4 > st->inlen) break; /*this might read data that isn't set yet, but should be safe*/ ((unsigned char*)&mask)[0] = ((unsigned char*)st->inbuffer)[payoffs+0]; ((unsigned char*)&mask)[1] = ((unsigned char*)st->inbuffer)[payoffs+1]; ((unsigned char*)&mask)[2] = ((unsigned char*)st->inbuffer)[payoffs+2]; ((unsigned char*)&mask)[3] = ((unsigned char*)st->inbuffer)[payoffs+3]; payoffs += 4; } /*if there isn't space, try again next time around*/ if (payoffs + paylen > st->inlen) break; if (mask) { int i; for (i = 0; i < paylen; i++) { ((unsigned char*)st->inbuffer)[i + payoffs] ^= ((unsigned char*)&mask)[i&3]; } } net_message.cursize = 0; switch((ctrl>>8) & 0xf) { case 0: /*continuation*/ Con_Printf ("websocket continuation frame from %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); goto closesvstream; case 1: /*text frame*/ // Con_Printf ("websocket text frame from %s\n", NET_AdrToString (adr, sizeof(adr), st->remoteaddr)); { /*text frames are pure utf-8 chars, no dodgy encodings or anything, all pre-checked... except we're trying to send binary data. so we need to unmask things (char 0 is encoded as 0x100 - truncate it) */ unsigned char *in = st->inbuffer+payoffs, *out = net_message_buffer; int len = paylen; while(len && out < net_message_buffer + sizeof(net_message_buffer)) { if ((*in & 0xe0)==0xc0 && len > 1) { *out = ((in[0] & 0x1f)<<6) | ((in[1] & 0x3f)<<0); in+=2; len -= 2; } else if (*in & 0x80) { *out = '?'; in++; len -= 1; } else { *out = in[0]; in++; len -= 1; } out++; } net_message.cursize = out - net_message_buffer; } break; case 2: /*binary frame*/ // Con_Printf ("websocket binary frame from %s\n", NET_AdrToString (adr, sizeof(adr), st->remoteaddr)); net_message.cursize = paylen; if (net_message.cursize >= sizeof(net_message_buffer) ) { Con_TPrintf (TL_OVERSIZEPACKETFROM, NET_AdrToString (adr, sizeof(adr), &net_from)); goto closesvstream; } memcpy(net_message_buffer, st->inbuffer+payoffs, paylen); break; case 8: /*connection close*/ Con_Printf ("websocket closure %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); goto closesvstream; case 9: /*ping*/ Con_Printf ("websocket ping from %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); goto closesvstream; case 10: /*pong*/ Con_Printf ("websocket pong from %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); goto closesvstream; default: Con_Printf ("Unsupported websocket opcode (%i) from %s\n", (ctrl>>8) & 0xf, NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); goto closesvstream; } // memcpy(net_message_buffer, st->inbuffer+2, net_message.cursize); memmove(st->inbuffer, st->inbuffer+payoffs + paylen, st->inlen - (payoffs + paylen)); st->inlen -= payoffs + paylen; if (net_message.cursize) { net_message.packing = SZ_RAWBYTES; net_message.currentbit = 0; net_from = st->remoteaddr; return true; } } break; } } if (con->generic.thesocket != INVALID_SOCKET && con->active < 256) { int newsock; fromlen = sizeof(from); newsock = accept(con->generic.thesocket, (struct sockaddr*)&from, &fromlen); if (newsock != INVALID_SOCKET) { int _true = true; ioctlsocket(newsock, FIONBIO, (u_long *)&_true); setsockopt(newsock, IPPROTO_TCP, TCP_NODELAY, (char *)&_true, sizeof(_true)); con->active++; st = Z_Malloc(sizeof(*con->tcpstreams)); st->clienttype = TCPC_UNKNOWN; st->next = con->tcpstreams; con->tcpstreams = st; st->socketnum = newsock; st->inlen = 0; /*grab the net address*/ SockadrToNetadr(&from, &st->remoteaddr); /*sockadr doesn't contain transport info, so fix that up here*/ if (st->remoteaddr.type == NA_IP) st->remoteaddr.type = NA_TCP; else if (st->remoteaddr.type == NA_IPV6) st->remoteaddr.type = NA_TCPV6; st->timeouttime = timeval + 30; } } return false; } qboolean FTENET_TCPConnect_SendPacket(ftenet_generic_connection_t *gcon, int length, void *data, netadr_t *to) { ftenet_tcpconnect_connection_t *con = (ftenet_tcpconnect_connection_t*)gcon; ftenet_tcpconnect_stream_t *st; for (st = con->tcpstreams; st; st = st->next) { if (st->socketnum == INVALID_SOCKET) continue; if (NET_CompareAdr(to, &st->remoteaddr)) { if (!st->outlen) { switch(st->clienttype) { case TCPC_QIZMO: { unsigned short slen = BigShort((unsigned short)length); if (st->outlen + sizeof(slen) + length > sizeof(st->outbuffer)) { Con_DPrintf("FTENET_TCPConnect_SendPacket: outgoing overflow\n"); } else { memcpy(st->outbuffer + st->outlen, &slen, sizeof(slen)); memcpy(st->outbuffer + st->outlen + sizeof(slen), data, length); st->outlen += sizeof(slen) + length; } } break; case TCPC_UNFRAMED: if (length > sizeof(st->outbuffer)) { Con_DPrintf("FTENET_TCPConnect_SendPacket: outgoing overflow\n"); } memcpy(st->outbuffer, data, length); st->outlen = length; break; case TCPC_WEBSOCKETU: case TCPC_WEBSOCKETB: { /*as a server, we don't need the mask stuff*/ unsigned short ctrl = (st->clienttype==TCPC_WEBSOCKETB)?0x8200:0x8100; unsigned int paylen = 0; unsigned int payoffs = 2; int i; switch((ctrl>>8) & 0xf) { case 1: for (i = 0; i < length; i++) { paylen += (((char*)data)[i] == 0 || ((unsigned char*)data)[i] >= 0x80)?2:1; } break; default: paylen = length; break; } if (paylen >= 126) { ctrl |= 126; payoffs += 2; } else ctrl |= paylen; st->outbuffer[0] = ctrl>>8; st->outbuffer[1] = ctrl&0xff; if (paylen >= 126) { st->outbuffer[2] = paylen>>8; st->outbuffer[3] = paylen&0xff; } switch((ctrl>>8) & 0xf) { case 1:/*utf8ify the data*/ for (i = 0; i < length; i++) { if (!((unsigned char*)data)[i]) { /*0 is encoded as 0x100 to avoid safety checks*/ st->outbuffer[payoffs++] = 0xc0 | (0x100>>6); st->outbuffer[payoffs++] = 0x80 | (0x100&0x3f); } else if (((unsigned char*)data)[i] >= 0x80) { /*larger bytes require markup*/ st->outbuffer[payoffs++] = 0xc0 | (((unsigned char*)data)[i]>>6); st->outbuffer[payoffs++] = 0x80 | (((unsigned char*)data)[i]&0x3f); } else { /*lower 7 bits are as-is*/ st->outbuffer[payoffs++] = ((char*)data)[i]; } } break; default: //raw data memcpy(st->outbuffer+payoffs, data, length); payoffs += length; break; } st->outlen = payoffs; } break; default: break; } } if (st->outlen) { /*try and flush the old data*/ int done; done = send(st->socketnum, st->outbuffer, st->outlen, 0); if (done > 0) { memmove(st->outbuffer, st->outbuffer + done, st->outlen - done); st->outlen -= done; } } st->timeouttime = Sys_DoubleTime() + 20; return true; } } return false; } void FTENET_TCPConnect_Close(ftenet_generic_connection_t *gcon) { ftenet_tcpconnect_connection_t *con = (ftenet_tcpconnect_connection_t*)gcon; ftenet_tcpconnect_stream_t *st; st = con->tcpstreams; while (con->tcpstreams) { st = con->tcpstreams; con->tcpstreams = st->next; if (st->socketnum != INVALID_SOCKET) closesocket(st->socketnum); BZ_Free(st); } FTENET_Generic_Close(gcon); } #ifdef HAVE_PACKET int FTENET_TCPConnect_SetReceiveFDSet(ftenet_generic_connection_t *gcon, fd_set *fdset) { int maxfd = 0; ftenet_tcpconnect_connection_t *con = (ftenet_tcpconnect_connection_t*)gcon; ftenet_tcpconnect_stream_t *st; for (st = con->tcpstreams; st; st = st->next) { if (st->socketnum == INVALID_SOCKET) continue; FD_SET(st->socketnum, fdset); // network socket if (maxfd < st->socketnum) maxfd = st->socketnum; } if (con->generic.thesocket != INVALID_SOCKET) { FD_SET(con->generic.thesocket, fdset); // network socket if (maxfd < con->generic.thesocket) maxfd = con->generic.thesocket; } return maxfd; } #endif ftenet_generic_connection_t *FTENET_TCPConnect_EstablishConnection(int affamily, qboolean isserver, const char *address) { //this is written to support either ipv4 or ipv6, depending on the remote addr. ftenet_tcpconnect_connection_t *newcon; unsigned long _true = true; int newsocket; int temp; netadr_t adr; struct sockaddr_qstorage qs; int family; if (!strncmp(address, "tcp://", 6)) address += 6; if (!strncmp(address, "ws://", 5)) address += 5; if (!strncmp(address, "wss://", 6)) address += 6; if (isserver) { #ifndef HAVE_PACKET //unable to listen on tcp if we have no packet interface return NULL; #else if (!NET_PortToAdr(affamily, address, &adr)) return NULL; //couldn't resolve the name if (adr.type == NA_IP) adr.type = NA_TCP; temp = NetadrToSockadr(&adr, &qs); family = ((struct sockaddr_in*)&qs)->sin_family; if ((newsocket = socket (family, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) { Con_Printf("operating system doesn't support that\n"); return NULL; } if ((bind(newsocket, (struct sockaddr *)&qs, temp) == INVALID_SOCKET) || (listen(newsocket, 2) == INVALID_SOCKET)) { SockadrToNetadr(&qs, &adr); //mneh, reuse qs. NET_AdrToString((char*)&qs, sizeof(qs), &adr); Con_Printf("Unable to listen at %s\n", (char*)&qs); closesocket(newsocket); return NULL; } if (ioctlsocket (newsocket, FIONBIO, &_true) == -1) Sys_Error ("UDP_OpenSocket: ioctl FIONBIO: %s", strerror(qerrno)); #endif } else { if (!NET_StringToAdr(address, 0, &adr)) return NULL; //couldn't resolve the name if (adr.type == NA_IP) adr.type = NA_TCP; newsocket = TCP_OpenStream(&adr); if (newsocket == INVALID_SOCKET) return NULL; } //this isn't fatal setsockopt(newsocket, IPPROTO_TCP, TCP_NODELAY, (char *)&_true, sizeof(_true)); newcon = Z_Malloc(sizeof(*newcon)); if (newcon) { if (isserver) newcon->generic.GetLocalAddress = FTENET_Generic_GetLocalAddress; newcon->generic.GetPacket = FTENET_TCPConnect_GetPacket; newcon->generic.SendPacket = FTENET_TCPConnect_SendPacket; newcon->generic.Close = FTENET_TCPConnect_Close; #ifdef HAVE_PACKET newcon->generic.SetReceiveFDSet = FTENET_TCPConnect_SetReceiveFDSet; #endif newcon->generic.islisten = isserver; newcon->generic.addrtype[0] = adr.type; newcon->generic.addrtype[1] = NA_INVALID; newcon->active = 0; if (!isserver) { newcon->generic.thesocket = INVALID_SOCKET; newcon->active++; newcon->tcpstreams = Z_Malloc(sizeof(*newcon->tcpstreams)); newcon->tcpstreams->next = NULL; newcon->tcpstreams->socketnum = newsocket; newcon->tcpstreams->inlen = 0; newcon->tcpstreams->remoteaddr = adr; #ifdef FTE_TARGET_WEB newcon->tcpstreams->clienttype = TCPC_UNFRAMED; #else //send the qizmo greeting. newcon->tcpstreams->clienttype = TCPC_UNKNOWN; send(newsocket, "qizmo\n", 6, 0); #endif newcon->tcpstreams->timeouttime = Sys_DoubleTime() + 30; } else { newcon->tcpstreams = NULL; newcon->generic.thesocket = newsocket; } return &newcon->generic; } else { closesocket(newsocket); return NULL; } } #ifdef IPPROTO_IPV6 ftenet_generic_connection_t *FTENET_TCP6Connect_EstablishConnection(qboolean isserver, const char *address) { return FTENET_TCPConnect_EstablishConnection(NA_TCPV6, isserver, address); } #endif ftenet_generic_connection_t *FTENET_TCP4Connect_EstablishConnection(qboolean isserver, const char *address) { return FTENET_TCPConnect_EstablishConnection(NA_TCP, isserver, address); } #endif #ifdef IRCCONNECT typedef struct ftenet_ircconnect_stream_s { char theiruser[16]; int inlen; char inbuffer[1500]; float timeouttime; netadr_t remoteaddr; struct ftenet_ircconnect_stream_s *next; } ftenet_ircconnect_stream_t; typedef struct { ftenet_generic_connection_t generic; netadr_t ircserver; char incoming[512+1]; int income; char ourusername[16]; char usechannel[16]; char outbuf[8192]; unsigned int outbufcount; ftenet_ircconnect_stream_t *streams; } ftenet_ircconnect_connection_t; qboolean FTENET_IRCConnect_GetPacket(ftenet_generic_connection_t *gcon) { unsigned char *s, *start, *end, *endl; int read; unsigned char *from; int fromlen; int code; char adr[128]; ftenet_ircconnect_connection_t *con = (ftenet_ircconnect_connection_t*)gcon; if (con->generic.thesocket == INVALID_SOCKET) { if (con->income == 0) { netadr_t ip; cvar_t *ircuser = Cvar_Get("ircuser", "none", 0, "IRC Connect"); cvar_t *ircnick = Cvar_Get("ircnick", "", 0, "IRC Connect"); cvar_t *ircsomething = Cvar_Get("ircsomething", "moo", 0, "IRC Connect"); cvar_t *ircclientaddr = Cvar_Get("ircclientaddr", "127.0.0.1", 0, "IRC Connect"); NET_StringToAdr(con->ircserver.address.irc.host, 6667, &ip); con->generic.thesocket = TCP_OpenStream(&ip); //when hosting, the specified nick is the name we're using. //when connecting, the specified nick is the name we're trying to send to, and our own name is inconsequential. if (con->generic.islisten && *con->ircserver.address.irc.user) Q_strncpyz(con->ourusername, con->ircserver.address.irc.user, sizeof(con->ourusername)); else Q_strncpyz(con->ourusername, ircnick->string, sizeof(con->ourusername)); if (!*con->ourusername) { Q_snprintfz(con->ourusername, sizeof(con->ourusername), "fte%x\n", rand()); } send(con->generic.thesocket, "USER ", 5, 0); send(con->generic.thesocket, ircuser->string, strlen(ircuser->string), 0); send(con->generic.thesocket, " ", 1, 0); send(con->generic.thesocket, con->ircserver.address.irc.host, strlen(con->ircserver.address.irc.host), 0); send(con->generic.thesocket, " ", 1, 0); send(con->generic.thesocket, ircclientaddr->string, strlen(ircclientaddr->string), 0); send(con->generic.thesocket, " :", 2, 0); send(con->generic.thesocket, ircsomething->string, strlen(ircsomething->string), 0); send(con->generic.thesocket, "\r\n", 2, 0); send(con->generic.thesocket, "NICK ", 5, 0); send(con->generic.thesocket, con->ourusername, strlen(con->ourusername), 0); send(con->generic.thesocket, "\r\n", 2, 0); } } else { read = recv(con->generic.thesocket, con->incoming+con->income, sizeof(con->incoming)-1 - con->income, 0); if (read < 0) { read = qerrno; switch(read) { case ECONNABORTED: case ECONNRESET: closesocket(con->generic.thesocket); con->generic.thesocket = INVALID_SOCKET; break; default: break; } read = 0;//return false; } else if (read == 0) //they disconnected. { closesocket(con->generic.thesocket); con->generic.thesocket = INVALID_SOCKET; } con->income += read; con->incoming[con->income] = 0; } start = con->incoming; end = start+con->income; while (start < end) { endl = NULL; for (s = start; s < end; s++) { if (*s == '\n') { endl = s; break; } } if (endl == NULL) //not got a complete command. break; s = start; while(*s == ' ') s++; if (*s == ':') { s++; from = s; while(s sizeof(net_from.address.irc.user)-1) fromlen = sizeof(net_from.address.irc.user)-1; for (code = 0; code < fromlen; code++) if (from[code] == '!') { fromlen = code; break; } net_from.type = NA_IRC; memcpy(net_from.address.irc.user, from, fromlen); net_from.address.irc.user[fromlen] = 0; dest = s; //discard the destination name while(s= sizeof(net_from.address.irc.channel)) { //no space, just pretend it was direct. net_from.address.irc.channel[0] = 0; } else { memcpy(net_from.address.irc.channel, dest, s-dest); net_from.address.irc.channel[s-dest] = 0; if (!strcmp(net_from.address.irc.channel, con->ourusername)) { //this was aimed at us. clear the channel. net_from.address.irc.channel[0] = 0; } } while(*s == ' ') s++; if (*s == ':') { s++; if (*s == '!') { s++; /*interpret as a connectionless packet*/ net_message.cursize = 4 + endl - s; if (net_message.cursize >= sizeof(net_message_buffer) ) { Con_TPrintf (TL_OVERSIZEPACKETFROM, NET_AdrToString (adr, sizeof(adr), &net_from)); break; } *(unsigned int*)net_message_buffer = ~0; memcpy(net_message_buffer+4, s, net_message.cursize); net_message.packing = SZ_RAWBYTES; net_message.currentbit = 0; //clean up the incoming data memmove(con->incoming, start, end - (endl+1)); con->income = end - (endl+1); con->incoming[con->income] = 0; return true; } if (*s == '$') { unsigned char *nstart = s; while (*s != '\r' && *s != '\n' && *s != '#' && *s != ' ' && *s != ':') s++; if (*s == '#') { if (strncmp(nstart, con->ourusername, strlen(con->ourusername)) || strlen(con->ourusername) != s - nstart) while(*s == '#') s++; } } if (*s == '#') { ftenet_ircconnect_stream_t *st; int psize; for (st = con->streams; st; st = st->next) { if (!strncmp(st->remoteaddr.address.irc.user, from, fromlen) && st->remoteaddr.address.irc.user[fromlen] == 0) break; } if (!st) { st = Z_Malloc(sizeof(*st)); st->remoteaddr = net_from; st->next = con->streams; con->streams = st; } //skip over the hash s++; psize = 0; if (*s >= 'a' && *s <= 'f') psize += *s - 'a' + 10; else if (*s >= '0' && *s <= '9') psize += *s - '0'; s++; psize*=16; if (*s >= 'a' && *s <= 'f') psize += *s - 'a' + 10; else if (*s >= '0' && *s <= '9') psize += *s - '0'; s++; psize*=16; if (*s >= 'a' && *s <= 'f') psize += *s - 'a' + 10; else if (*s >= '0' && *s <= '9') psize += *s - '0'; s++; while (s < endl && st->inlen < sizeof(st->inbuffer)) { switch (*s) { //handle markup case '\\': s++; if (s < endl) { switch(*s) { case '\\': st->inbuffer[st->inlen++] = *s; break; case 'n': st->inbuffer[st->inlen++] = '\n'; break; case 'r': st->inbuffer[st->inlen++] = '\r'; break; case '0': st->inbuffer[st->inlen++] = 0; break; default: st->inbuffer[st->inlen++] = '?'; break; } } break; //ignore these case '\n': case '\r': case '\0': //this one doesn't have to be ignored. break; //handle normal char default: st->inbuffer[st->inlen++] = *s; break; } s++; } if (st->inlen > psize || psize >= sizeof(net_message_buffer) ) { st->inlen = 0; Con_Printf ("Corrupt packet from %s\n", NET_AdrToString (adr, sizeof(adr), &net_from)); } else if (st->inlen == psize) { /*interpret as a connectionless packet*/ net_message.cursize = st->inlen; if (net_message.cursize >= sizeof(net_message_buffer) ) { Con_TPrintf (TL_OVERSIZEPACKETFROM, NET_AdrToString (adr, sizeof(adr), &net_from)); break; } memcpy(net_message_buffer, st->inbuffer, net_message.cursize); net_message.packing = SZ_RAWBYTES; net_message.currentbit = 0; st->inlen = 0; //clean up the incoming data memmove(con->incoming, start, end - (endl+1)); con->income = end - (endl+1); con->incoming[con->income] = 0; return true; } } } } else if (!strncmp(s, "PING ", 5)) { send(con->generic.thesocket, "PONG ", 5, 0); send(con->generic.thesocket, s+5, endl - s - 5, 0); send(con->generic.thesocket, "\r\n", 2, 0); } else { code = strtoul(s, (char **)&s, 10); switch (code) { case 001: { if (con->ircserver.address.irc.channel) { send(con->generic.thesocket, "JOIN ", 5, 0); send(con->generic.thesocket, con->ircserver.address.irc.channel, strlen(con->ircserver.address.irc.channel), 0); send(con->generic.thesocket, "\r\n", 2, 0); } } break; case 433: //nick already in use send(con->generic.thesocket, "NICK ", 5, 0); { cvar_t *ircnick2 = Cvar_Get("ircnick2", "YIBBLE", 0, "IRC Connect"); Q_strncpyz(con->ourusername, ircnick2->string, sizeof(con->ourusername)); send(con->generic.thesocket, con->ourusername, strlen(con->ourusername), 0); } send(con->generic.thesocket, "\r\n", 2, 0); break; case 0: //non-numerical event. break; } } while(*s == ' ') s++; start = s = endl+1; } memmove(con->incoming, start, end - start); con->income = end - start; con->incoming[con->income] = 0; if (con->generic.thesocket == INVALID_SOCKET) con->income = 0; return false; } qboolean FTENET_IRCConnect_SendPacket(ftenet_generic_connection_t *gcon, int length, void *data, netadr_t *to) { ftenet_ircconnect_connection_t *con = (ftenet_ircconnect_connection_t*)gcon; unsigned char *buffer; unsigned char *lenofs; int packed; int fulllen = length; int newoutcount; for (packed = 0; packed < FTENET_ADDRTYPES; packed++) if (to->type == con->generic.addrtype[packed]) break; if (packed == FTENET_ADDRTYPES) return false; packed = 0; if (con->generic.thesocket == INVALID_SOCKET) return true; /* if (*(unsigned int *)data == ~0 && !strchr(data, '\n') && !strchr(data, '\r') && strlen(data) == length) { if (send(con->generic.thesocket, va("PRIVMSG %s :!", to.address.irc.user), 15, 0) != 15) Con_Printf("bad send\n"); else if (send(con->generic.thesocket, (char*)data+4, length - 4, 0) != length-4) Con_Printf("bad send\n"); else if (send(con->generic.thesocket, "\r\n", 2, 0) != 2) Con_Printf("bad send\n"); return true; } */ newoutcount = con->outbufcount; if (!con->outbufcount) while(length) { buffer = con->outbuf + newoutcount; if (*to->address.irc.channel) { int unamelen; int chanlen; unamelen = strlen(to->address.irc.user); chanlen = strlen(to->address.irc.channel); packed = 8+chanlen+3+unamelen+1 + 3; if (packed+1 + newoutcount > sizeof(con->outbuf)) break; memcpy(buffer, "PRIVMSG ", 8); memcpy(buffer+8, to->address.irc.channel, chanlen); memcpy(buffer+8+chanlen, " :$", 3); memcpy(buffer+8+chanlen+3, to->address.irc.user, unamelen); memcpy(buffer+8+chanlen+3+unamelen, "#", 1); lenofs = buffer+8+chanlen+3+unamelen+1; sprintf(lenofs, "%03x", fulllen); } else { int unamelen; unamelen = strlen(to->address.irc.user); packed = 8 + unamelen + 3 + 3; if (packed+1 + newoutcount > sizeof(con->outbuf)) break; memcpy(buffer, "PRIVMSG ", 8); memcpy(buffer+8, to->address.irc.user, unamelen); memcpy(buffer+8+unamelen, " :#", 3); lenofs = buffer+8+unamelen+3; sprintf(lenofs, "%03x", fulllen); } while(length && packed < 400 && packed+newoutcount < sizeof(con->outbuf)-2) //make sure there's always space { switch(*(unsigned char*)data) { case '\\': buffer[packed++] = '\\'; buffer[packed++] = '\\'; break; case '\n': buffer[packed++] = '\\'; buffer[packed++] = 'n'; break; case '\r': buffer[packed++] = '\\'; buffer[packed++] = 'r'; break; case '\0': buffer[packed++] = '\\'; buffer[packed++] = '0'; break; default: buffer[packed++] = *(unsigned char*)data; break; } length--; data = (char*)data + 1; } buffer[packed++] = '\r'; buffer[packed++] = '\n'; newoutcount += packed; packed = 0; } if (!length) { //only if we flushed all con->outbufcount = newoutcount; } //try and flush it length = send(con->generic.thesocket, con->outbuf, con->outbufcount, 0); if (length > 0) { memmove(con->outbuf, con->outbuf+length, con->outbufcount-length); con->outbufcount -= length; } return true; } void FTENET_IRCConnect_Close(ftenet_generic_connection_t *gcon) { ftenet_ircconnect_connection_t *con = (ftenet_ircconnect_connection_t *)gcon; ftenet_ircconnect_stream_t *st; while(con->streams) { st = con->streams; con->streams = st->next; Z_Free(st); } FTENET_Generic_Close(gcon); } struct ftenet_generic_connection_s *FTENET_IRCConnect_EstablishConnection(qboolean isserver, const char *address) { //this is written to support either ipv4 or ipv6, depending on the remote addr. ftenet_ircconnect_connection_t *newcon; netadr_t adr; if (!NET_StringToAdr(address, 6667, &adr)) return NULL; //couldn't resolve the name newcon = Z_Malloc(sizeof(*newcon)); if (newcon) { newcon->generic.GetPacket = FTENET_IRCConnect_GetPacket; newcon->generic.SendPacket = FTENET_IRCConnect_SendPacket; newcon->generic.Close = FTENET_IRCConnect_Close; newcon->generic.islisten = isserver; newcon->generic.addrtype[0] = NA_IRC; newcon->generic.addrtype[1] = NA_INVALID; newcon->generic.thesocket = INVALID_SOCKET; newcon->ircserver = adr; return &newcon->generic; } else { return NULL; } } #endif #ifdef HAVE_WEBSOCKCL #include #include #include #include #include #include extern PPB_Core *ppb_core; extern PPB_WebSocket *ppb_websocket_interface; extern PPB_Var *ppb_var_interface; extern PP_Instance pp_instance; typedef struct { ftenet_generic_connection_t generic; PP_Resource sock; netadr_t remoteadr; qboolean havepacket; struct PP_Var incomingpacket; qboolean failed; } ftenet_websocket_connection_t; static void websocketgot(void *user_data, int32_t result) { ftenet_websocket_connection_t *wsc = user_data; if (result == PP_OK) { wsc->havepacket = true; } else { Sys_Printf("%s: %i\n", __func__, result); wsc->failed = true; } } static void websocketconnected(void *user_data, int32_t result) { ftenet_websocket_connection_t *wsc = user_data; if (result == PP_OK) { int res; //we got a successful connection, enable reception. struct PP_CompletionCallback ccb = {websocketgot, wsc, PP_COMPLETIONCALLBACK_FLAG_OPTIONAL}; res = ppb_websocket_interface->ReceiveMessage(wsc->sock, &wsc->incomingpacket, ccb); if (res != PP_OK_COMPLETIONPENDING) websocketgot(wsc, res); } else { Sys_Printf("%s: %i\n", __func__, result); //some sort of error connecting, make it timeout now wsc->failed = true; } } static void websocketclosed(void *user_data, int32_t result) { ftenet_websocket_connection_t *wsc = user_data; if (wsc->havepacket) { wsc->havepacket = false; ppb_var_interface->Release(wsc->incomingpacket); } ppb_core->ReleaseResource(wsc->sock); // Z_Free(wsc); } static void FTENET_WebSocket_Close(ftenet_generic_connection_t *gcon) { int res; /*meant to free the memory too, in this case we get the callback to do it*/ ftenet_websocket_connection_t *wsc = (void*)gcon; struct PP_CompletionCallback ccb = {websocketclosed, wsc, PP_COMPLETIONCALLBACK_FLAG_NONE}; ppb_websocket_interface->Close(wsc->sock, PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, PP_MakeUndefined(), ccb); } static qboolean FTENET_WebSocket_GetPacket(ftenet_generic_connection_t *gcon) { ftenet_websocket_connection_t *wsc = (void*)gcon; int res; int len = 0; if (wsc->havepacket) { unsigned char *utf8 = (unsigned char *)ppb_var_interface->VarToUtf8(wsc->incomingpacket, &len); unsigned char *out = (unsigned char *)net_message_buffer; wsc->havepacket = false; memcpy(&net_from, &wsc->remoteadr, sizeof(net_from)); while(len && out < net_message_buffer + sizeof(net_message_buffer)) { if ((*utf8 & 0xe0)==0xc0 && len > 1) { *out = ((utf8[0] & 0x1f)<<6) | ((utf8[1] & 0x3f)<<0); utf8+=2; len -= 2; } else if (*utf8 & 0x80) { *out = '?'; utf8++; len -= 1; } else { *out = utf8[0]; utf8++; len -= 1; } out++; } net_message.cursize = out - net_message_buffer; ppb_var_interface->Release(wsc->incomingpacket); if (!wsc->failed) { //get the next one struct PP_CompletionCallback ccb = {websocketgot, wsc, PP_COMPLETIONCALLBACK_FLAG_OPTIONAL}; res = ppb_websocket_interface->ReceiveMessage(wsc->sock, &wsc->incomingpacket, ccb); if (res != PP_OK_COMPLETIONPENDING) websocketgot(wsc, res); } if (len) { char adr[64]; Con_TPrintf (TL_OVERSIZEPACKETFROM, NET_AdrToString (adr, sizeof(adr), &net_from)); return false; } return true; } return false; } static qboolean FTENET_WebSocket_SendPacket(ftenet_generic_connection_t *gcon, int length, void *data, netadr_t *to) { ftenet_websocket_connection_t *wsc = (void*)gcon; int res; int outchars = 0; unsigned char outdata[length*2+1]; unsigned char *out=outdata, *in=data; if (wsc->failed) return false; while(length-->0) { if (!*in) { //sends 256 instead of 0 *out++ = 0xc0 | (0x100 >> 6); *out++ = 0x80 | (0x100 & 0x3f); } else if (*in >= 0x80) { *out++ = 0xc0 | (*in >> 6); *out++ = 0x80 | (*in & 0x3f); } else *out++ = *in; in++; outchars++; } *out = 0; struct PP_Var str = ppb_var_interface->VarFromUtf8(outdata, out - outdata); res = ppb_websocket_interface->SendMessage(wsc->sock, str); // Sys_Printf("FTENET_WebSocket_SendPacket: result %i\n", res); ppb_var_interface->Release(str); return true; } /*nacl websockets implementation...*/ static ftenet_generic_connection_t *FTENET_WebSocket_EstablishConnection(qboolean isserver, const char *address) { ftenet_websocket_connection_t *newcon; netadr_t adr; PP_Resource newsocket; if (isserver || !ppb_websocket_interface) { return NULL; } if (!NET_StringToAdr(address, 80, &adr)) return NULL; //couldn't resolve the name newcon = Z_Malloc(sizeof(*newcon)); if (newcon) { struct PP_CompletionCallback ccb = {websocketconnected, newcon, PP_COMPLETIONCALLBACK_FLAG_NONE}; newsocket = ppb_websocket_interface->Create(pp_instance); struct PP_Var str = ppb_var_interface->VarFromUtf8(adr.address.websocketurl, strlen(adr.address.websocketurl)); ppb_websocket_interface->Connect(newsocket, str, NULL, 0, ccb); ppb_var_interface->Release(str); Q_strncpyz(newcon->generic.name, "WebSocket", sizeof(newcon->generic.name)); newcon->generic.GetPacket = FTENET_WebSocket_GetPacket; newcon->generic.SendPacket = FTENET_WebSocket_SendPacket; newcon->generic.Close = FTENET_WebSocket_Close; newcon->generic.islisten = isserver; newcon->generic.addrtype[0] = NA_WEBSOCKET; newcon->generic.addrtype[1] = NA_INVALID; newcon->generic.thesocket = INVALID_SOCKET; newcon->sock = newsocket; newcon->remoteadr = adr; return &newcon->generic; } return NULL; } #endif /*firstsock is a cookie*/ int NET_GetPacket (netsrc_t netsrc, int firstsock) { ftenet_connections_t *collection; if (netsrc == NS_SERVER) { #ifdef CLIENTONLY Sys_Error("NET_GetPacket: Bad netsrc"); collection = NULL; #else collection = svs.sockets; #endif } else { #ifdef SERVERONLY Sys_Error("NET_GetPacket: Bad netsrc"); collection = NULL; #else collection = cls.sockets; #endif } if (!collection) return -1; while (firstsock < MAX_CONNECTIONS) { if (!collection->conn[firstsock]) break; if (collection->conn[firstsock]->GetPacket(collection->conn[firstsock])) { if (net_fakeloss.value) { if (frandom () < net_fakeloss.value) continue; } net_from.connum = firstsock+1; return firstsock; } firstsock += 1; } return -1; } int NET_LocalAddressForRemote(ftenet_connections_t *collection, netadr_t *remote, netadr_t *local, int idx) { if (!remote->connum) return 0; if (!collection->conn[remote->connum-1]) return 0; if (!collection->conn[remote->connum-1]->GetLocalAddress) return 0; return collection->conn[remote->connum-1]->GetLocalAddress(collection->conn[remote->connum-1], local, idx); } void NET_SendPacket (netsrc_t netsrc, int length, void *data, netadr_t *to) { char buffer[64]; ftenet_connections_t *collection; int i; if (netsrc == NS_SERVER) { #ifdef CLIENTONLY Sys_Error("NET_GetPacket: Bad netsrc"); return; #else collection = svs.sockets; #endif } else { #ifdef SERVERONLY Sys_Error("NET_GetPacket: Bad netsrc"); return; #else collection = cls.sockets; #endif } if (!collection) return; if (net_fakeloss.value) { if (frandom () < net_fakeloss.value) return; } if (to->connum) { if (collection->conn[to->connum-1]) if (collection->conn[to->connum-1]->SendPacket(collection->conn[to->connum-1], length, data, to)) return; } for (i = 0; i < MAX_CONNECTIONS; i++) { if (!collection->conn[i]) continue; if (collection->conn[i]->SendPacket(collection->conn[i], length, data, to)) return; } Con_Printf("No route to %s - try reconnecting\n", NET_AdrToString(buffer, sizeof(buffer), to)); } qboolean NET_EnsureRoute(ftenet_connections_t *collection, char *routename, char *host, qboolean islisten) { netadr_t adr; NET_StringToAdr(host, 0, &adr); switch(adr.type) { case NA_WEBSOCKET: case NA_TCP: case NA_TCPV6: case NA_IRC: if (!FTENET_AddToCollection(collection, routename, host, adr.type, islisten)) return false; break; default: //not recognised, or not needed break; } return true; } void NET_PrintAddresses(ftenet_connections_t *collection) { int i; int adrno, adrcount=1; netadr_t adr; char adrbuf[MAX_ADR_SIZE]; if (!collection) return; for (i = 0; i < MAX_CONNECTIONS; i++) { if (!collection->conn[i]) continue; adrno = 0; if (collection->conn[i]->GetLocalAddress) { for (adrcount=1; (adrcount = collection->conn[i]->GetLocalAddress(collection->conn[i], &adr, adrno)) && adrno < adrcount; adrno++) { Con_Printf("net address (%s): %s\n", collection->conn[i]->name, NET_AdrToString(adrbuf, sizeof(adrbuf), &adr)); } } if (!adrno) Con_Printf("net address (%s): no addresses\n", collection->conn[i]->name); } } //============================================================================= int TCP_OpenStream (netadr_t *remoteaddr) { #ifndef HAVE_TCP return INVALID_SOCKET; #else unsigned long _true = true; int newsocket; int temp; struct sockaddr_qstorage qs; // struct sockaddr_qstorage loc; int recvbufsize = (1<<19);//512kb temp = NetadrToSockadr(remoteaddr, &qs); if ((newsocket = socket (((struct sockaddr_in*)&qs)->sin_family, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) return (int)INVALID_SOCKET; setsockopt(newsocket, SOL_SOCKET, SO_RCVBUF, (void*)&recvbufsize, sizeof(recvbufsize)); if (ioctlsocket (newsocket, FIONBIO, &_true) == -1) Sys_Error ("UDP_OpenSocket: ioctl FIONBIO: %s", strerror(qerrno)); // memset(&loc, 0, sizeof(loc)); // ((struct sockaddr*)&loc)->sa_family = ((struct sockaddr*)&loc)->sa_family; // bind(newsocket, (struct sockaddr *)&loc, ((struct sockaddr_in*)&qs)->sin_family == AF_INET?sizeof(struct sockaddr_in):sizeof(struct sockaddr_in6)); if (connect(newsocket, (struct sockaddr *)&qs, temp) == INVALID_SOCKET) { int err = qerrno; if (err != EWOULDBLOCK && err != EINPROGRESS) { char buf[256]; NET_AdrToString(buf, sizeof(buf), remoteaddr); if (err == EADDRNOTAVAIL) { if (remoteaddr->port == 0 && (remoteaddr->type == NA_IP || remoteaddr->type == NA_IPV6)) Con_Printf ("TCP_OpenStream: no port specified (%s)\n", buf); else Con_Printf ("TCP_OpenStream: invalid address trying to connect to %s\n", buf); } else if (err == EACCES) Con_Printf ("TCP_OpenStream: access denied: check firewall (%s)\n", buf); else Con_Printf ("TCP_OpenStream: connect: error %i (%s)\n", err, buf); closesocket(newsocket); return (int)INVALID_SOCKET; } } return newsocket; #endif } /*int TCP_OpenListenSocket (const char *localip, int port) { #ifndef HAVE_TCP return INVALID_SOCKET; #else int newsocket; struct sockaddr_qstorage address; int pf; unsigned long _true = true; int i; int maxport = port + 100; if (localip && *localip) { if (!NET_StringToSockaddr(localip, port, &address, &pf, &adrsize)) return INVALID_SOCKET; } else { adrsize = sizeof(struct sockaddr_in); pf = ((struct sockaddr_in*)&address)->sin_family = AF_INET; ((struct sockaddr_in*)&address)->sin_port = htons(port); //ZOID -- check for interface binding option if ((i = COM_CheckParm("-ip")) != 0 && i < com_argc) { ((struct sockaddr_in*)&address)->sin_addr.s_addr = inet_addr(com_argv[i+1]); Con_TPrintf(TL_NETBINDINTERFACE, inet_ntoa(address.sin_addr)); } else ((struct sockaddr_in*)&address)->sin_addr.s_addr = INADDR_ANY; } if ((newsocket = socket (pf, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) return INVALID_SOCKET; if (ioctlsocket (newsocket, FIONBIO, &_true) == -1) Sys_Error ("TCP_OpenListenSocket: ioctl FIONBIO: %s", strerror(qerrno)); for(;;) { if (port == PORT_ANY) address.sin_port = 0; else address.sin_port = htons((short)port); if( bind (newsocket, (void *)&address, sizeof(address)) == -1) { if (!port) { Con_Printf("Cannot bind tcp socket\n"); closesocket(newsocket); return INVALID_SOCKET; } port++; if (port > maxport) { Con_Printf("Cannot bind tcp socket\n"); closesocket(newsocket); return INVALID_SOCKET; } } else break; } if (listen(newsocket, 1) == INVALID_SOCKET) { Con_Printf("Cannot listen on tcp socket\n"); closesocket(newsocket); return INVALID_SOCKET; } return newsocket; #endif } */ #if defined(SV_MASTER) || defined(CL_MASTER) int UDP_OpenSocket (int port, qboolean bcast) { SOCKET newsocket; struct sockaddr_in address; unsigned long _true = true; int i; int maxport = port + 100; if ((newsocket = socket (PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET) return (int)INVALID_SOCKET; if (ioctlsocket (newsocket, FIONBIO, &_true) == -1) Sys_Error ("UDP_OpenSocket: ioctl FIONBIO: %s", strerror(qerrno)); if (bcast) { _true = true; if (setsockopt(newsocket, SOL_SOCKET, SO_BROADCAST, (char *)&_true, sizeof(_true)) == -1) { Con_Printf("Cannot create broadcast socket\n"); return (int)INVALID_SOCKET; } } address.sin_family = AF_INET; //ZOID -- check for interface binding option if ((i = COM_CheckParm("-ip")) != 0 && i < com_argc) { address.sin_addr.s_addr = inet_addr(com_argv[i+1]); Con_TPrintf(TL_NETBINDINTERFACE, inet_ntoa(address.sin_addr)); } else address.sin_addr.s_addr = INADDR_ANY; for(;;) { if (port == PORT_ANY) address.sin_port = 0; else address.sin_port = htons((short)port); if( bind (newsocket, (void *)&address, sizeof(address)) == -1) { if (!port) Sys_Error ("UDP_OpenSocket: bind: %s", strerror(qerrno)); port++; if (port > maxport) Sys_Error ("UDP_OpenSocket: bind: %s", strerror(qerrno)); } else break; } return newsocket; } #ifdef IPPROTO_IPV6 int UDP6_OpenSocket (int port, qboolean bcast) { int err; SOCKET newsocket; struct sockaddr_in6 address; unsigned long _true = true; // int i; int maxport = port + 100; memset(&address, 0, sizeof(address)); if ((newsocket = socket (PF_INET6, SOCK_DGRAM, 0)) == INVALID_SOCKET) { Con_Printf("IPV6 is not supported: %s\n", strerror(qerrno)); return (int)INVALID_SOCKET; } if (ioctlsocket (newsocket, FIONBIO, &_true) == -1) Sys_Error ("UDP_OpenSocket: ioctl FIONBIO: %s", strerror(qerrno)); if (bcast) { // address.sin6_addr // _true = true; // if (setsockopt(newsocket, SOL_SOCKET, IP_ADD_MEMBERSHIP, (char *)&_true, sizeof(_true)) == -1) // { Con_Printf("Cannot create broadcast socket\n"); closesocket(newsocket); return (int)INVALID_SOCKET; // } } #ifdef IPV6_V6ONLY setsockopt(newsocket, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&_true, sizeof(_true)); #endif address.sin6_family = AF_INET6; //ZOID -- check for interface binding option // if ((i = COM_CheckParm("-ip6")) != 0 && i < com_argc) { // address.sin6_addr = inet_addr(com_argv[i+1]); /// Con_TPrintf(TL_NETBINDINTERFACE, // inet_ntoa(address.sin6_addr)); // } else memset(&address.sin6_addr, 0, sizeof(struct in6_addr)); for(;;) { if (port == PORT_ANY) address.sin6_port = 0; else address.sin6_port = htons((short)port); if( bind (newsocket, (void *)&address, sizeof(address)) == -1) { if (!port) { err = qerrno; Con_Printf ("UDP6_OpenSocket: bind: (%i) %s", err, strerror(err)); closesocket(newsocket); return (int)INVALID_SOCKET; } port++; if (port > maxport) { err = qerrno; Con_Printf ("UDP6_OpenSocket: bind: (%i) %s", err, strerror(err)); closesocket(newsocket); return (int)INVALID_SOCKET; } } else break; } return newsocket; } #endif void UDP_CloseSocket (int socket) { closesocket(socket); } int IPX_OpenSocket (int port, qboolean bcast) { #ifndef USEIPX return 0; #else SOCKET newsocket; struct sockaddr_ipx address; u_long _true = 1; if ((newsocket = socket (PF_IPX, SOCK_DGRAM, NSPROTO_IPX)) == INVALID_SOCKET) { if (qerrno != EAFNOSUPPORT) Con_Printf ("WARNING: IPX_Socket: socket: %i\n", qerrno); return INVALID_SOCKET; } // make it non-blocking if (ioctlsocket (newsocket, FIONBIO, &_true) == -1) { Con_Printf ("WARNING: IPX_Socket: ioctl FIONBIO: %i\n", qerrno); return INVALID_SOCKET; } if (bcast) { // make it broadcast capable if (setsockopt(newsocket, SOL_SOCKET, SO_BROADCAST, (char *)&_true, sizeof(_true)) == -1) { Con_Printf ("WARNING: IPX_Socket: setsockopt SO_BROADCAST: %i\n", qerrno); return INVALID_SOCKET; } } address.sa_family = AF_IPX; memset (address.sa_netnum, 0, 4); memset (address.sa_nodenum, 0, 6); if (port == PORT_ANY) address.sa_socket = 0; else address.sa_socket = htons((short)port); if( bind (newsocket, (void *)&address, sizeof(address)) == -1) { Con_Printf ("WARNING: IPX_Socket: bind: %i\n", qerrno); closesocket (newsocket); return INVALID_SOCKET; } return newsocket; #endif } void IPX_CloseSocket (int socket) { #ifdef USEIPX closesocket(socket); #endif } #endif // sleeps msec or until net socket is ready //stdin can sometimes be a socket. As a result, //we give the option to select it for nice console imput with timeouts. #ifndef CLIENTONLY qboolean NET_Sleep(int msec, qboolean stdinissocket) { #ifdef HAVE_PACKET struct timeval timeout; fd_set fdset; int maxfd; int con, sock; FD_ZERO(&fdset); if (stdinissocket) FD_SET(0, &fdset); //stdin tends to be socket 0 maxfd = 0; if (svs.sockets) for (con = 0; con < MAX_CONNECTIONS; con++) { if (!svs.sockets->conn[con]) continue; if (svs.sockets->conn[con]->SetReceiveFDSet) { sock = svs.sockets->conn[con]->SetReceiveFDSet(svs.sockets->conn[con], &fdset); if (sock > maxfd) maxfd = sock; } else { sock = svs.sockets->conn[con]->thesocket; if (sock != INVALID_SOCKET) { FD_SET(sock, &fdset); // network socket if (sock > maxfd) maxfd = sock; } } } timeout.tv_sec = msec/1000; timeout.tv_usec = (msec%1000)*1000; select(maxfd+1, &fdset, NULL, NULL, &timeout); if (stdinissocket) return FD_ISSET(0, &fdset); #endif return true; } #endif void NET_GetLocalAddress (int socket, netadr_t *out) { #ifndef HAVE_PACKET out->type = NA_INVALID; #else char buff[512]; char adrbuf[MAX_ADR_SIZE]; struct sockaddr_qstorage address; int namelen; netadr_t adr = {0}; qboolean notvalid = false; strcpy(buff, "localhost"); gethostname(buff, 512); buff[512-1] = 0; if (!NET_StringToAdr (buff, 0, &adr)) //urm NET_StringToAdr ("127.0.0.1", 0, &adr); namelen = sizeof(address); if (getsockname (socket, (struct sockaddr *)&address, &namelen) == -1) { notvalid = true; NET_StringToSockaddr("0.0.0.0", 0, (struct sockaddr_qstorage *)&address, NULL, NULL); // Sys_Error ("NET_Init: getsockname:", strerror(qerrno)); } SockadrToNetadr(&address, out); if (out->type == NA_IP) { if (!*(int*)out->address.ip) //socket was set to auto *(int *)out->address.ip = *(int *)adr.address.ip; //change it to what the machine says it is, rather than the socket. } if (notvalid) Con_Printf("Couldn't detect local ip\n"); else Con_TPrintf(TL_IPADDRESSIS, NET_AdrToString (adrbuf, sizeof(adrbuf), out) ); #endif } typedef struct { unsigned short msgtype; unsigned short msglen; unsigned int magiccookie; unsigned int transactid[3]; } stunhdr_t; typedef struct { unsigned short attrtype; unsigned short attrlen; } stunattr_t; #define SUPPORT_ICE #ifdef SUPPORT_ICE /* Interactive Connectivity Establishment (rfc 5245) find out your peer's potential ports. spam your peer with stun packets. see what sticks. the 'controller' assigns some final candidate pair to ensure that both peers send+receive from a single connection. if no candidates are available, try using stun to find public nat addresses. in fte, a 'pair' is actually in terms of each local socket and remote address. hopefully that won't cause too much weirdness. stun test packets must contain all sorts of info. username+integrity+fingerprint for validation. priority+usecandidate+icecontrol(ing) to decree the priority of any new remote candidates, whether its finished, and just who decides whether its finished. peers don't like it when those are missing. host candidates - addresses that are directly known server reflexive candidates - addresses that we found from some public stun server peer reflexive candidates - addresses that our peer finds out about as we spam them relayed candidates - some sort of socks5 or something proxy. */ struct icecandidate_s { struct icecandinfo_s info; struct icecandidate_s *next; netadr_t peer; //peer needs telling or something. qboolean dirty; //these are bitmasks. one bit for each local socket. unsigned int reachable; unsigned int tried; }; struct icestate_s { struct icestate_s *next; void *module; netadr_t chosenpeer; netadr_t pubstunserver; unsigned int stunretry; //once a second, extended to once a minite on reply char *stunserver;//where to get our public ip from. int stunport; unsigned int stunrnd[3]; unsigned int timeout; //time when we consider the connection dead unsigned int keepalive; //sent periodically... unsigned int retries; //bumped after each round of connectivity checks. affects future intervals. enum iceproto_e proto; enum icemode_e mode; qboolean controlled; //controller chooses final ports. enum icestate_e state; char *conname; //internal id. char *friendlyname; //who you're talking to. struct icecandidate_s *lc; char *lpwd; char *lufrag; struct icecandidate_s *rc; char *rpwd; char *rufrag; unsigned int tiehigh; unsigned int tielow; char *codec[32]; //96-127. don't really need to care about other ones. }; static struct icestate_s *icelist; #if !defined(SERVERONLY) && defined(VOICECHAT) extern cvar_t cl_voip_send; struct rtpheader_s { unsigned char v2_p1_x1_cc4; unsigned char m1_pt7; unsigned short seq; unsigned int timestamp; unsigned int ssrc; unsigned int csrc[1]; //sized according to cc }; void S_Voip_RTP_Parse(unsigned short sequence, char *codec, unsigned char *data, unsigned int datalen); qboolean NET_RTP_Parse(void) { struct rtpheader_s *rtpheader = (void*)net_message.data; if (net_message.cursize >= sizeof(*rtpheader) && (rtpheader->v2_p1_x1_cc4 & 0xc0) == 0x80) { int hlen; int padding = 0; struct icestate_s *con; int proto; //make sure this really came from an accepted rtp stream //note that an rtp connection equal to the game connection will likely mess up when sequences start to get big //(especially problematic in sane clients that start with a random sequence) for (con = icelist; con; con = con->next) { if (con->state != ICE_INACTIVE && con->proto == ICEP_VOICE && NET_CompareAdr(&net_from, &con->chosenpeer)) break; } //and continue with parsing it if its okay. if (con) { proto = rtpheader->m1_pt7 & 0x7f; if (proto < 96 || proto > 127) return false; proto -= 96; if (rtpheader->v2_p1_x1_cc4 & 0x20) padding = net_message.data[net_message.cursize-1]; hlen = sizeof(*rtpheader); hlen += ((rtpheader->v2_p1_x1_cc4 & 0xf)-1) * sizeof(int); S_Voip_RTP_Parse((unsigned short)BigShort(rtpheader->seq), con->codec[proto], hlen+(char*)(rtpheader), net_message.cursize - padding - hlen); return true; } } return false; } qboolean NET_RTP_Active(void) { struct icestate_s *con; for (con = icelist; con; con = con->next) { if (con->state == ICE_CONNECTED && con->proto == ICEP_VOICE) return true; } return false; } qboolean NET_RTP_Transmit(unsigned int sequence, unsigned int timestamp, char *codec, char *cdata, int clength) { sizebuf_t buf; char pdata[512]; int i; struct icestate_s *con; qboolean built = false; memset(&buf, 0, sizeof(buf)); buf.maxsize = sizeof(pdata); buf.cursize = 0; buf.allowoverflow = true; buf.data = pdata; for (con = icelist; con; con = con->next) { if (con->state == ICE_CONNECTED && con->proto == ICEP_VOICE) { for (i = 0; i < sizeof(con->codec)/sizeof(con->codec[0]); i++) { if (con->codec[i] && !strcmp(con->codec[i], codec)) { if (!built) { built = true; MSG_WriteByte(&buf, (2u<<6) | (0u<<5) | (0u<<4) | (0<<0)); //v2_p1_x1_cc4 MSG_WriteByte(&buf, (0u<<7) | ((i+96)<<0)); //m1_pt7 MSG_WriteShort(&buf, BigShort(sequence)); //seq MSG_WriteLong(&buf, BigLong(timestamp)); //timestamp MSG_WriteLong(&buf, BigLong(0)); //ssrc SZ_Write(&buf, cdata, clength); if (buf.overflowed) return built; } NET_SendPacket(NS_CLIENT, buf.cursize, buf.data, &con->chosenpeer); break; } } } } return built; } #endif struct icestate_s *QDECL ICE_Find(void *module, char *conname) { struct icestate_s *con; for (con = icelist; con; con = con->next) { if (con->module == module && !strcmp(con->conname, conname)) return con; } return NULL; } struct icestate_s *QDECL ICE_Create(void *module, char *conname, char *peername, enum icemode_e mode, enum iceproto_e proto) { ftenet_connections_t *collection; struct icestate_s *con; //only allow modes that we actually support. if (mode != ICEM_RAW && mode != ICEM_ICE) return NULL; //only allow protocols that we actually support. switch(proto) { default: return NULL; #if !defined(SERVERONLY) && defined(VOICECHAT) case ICEP_VOICE: collection = cls.sockets; break; #endif #ifndef SERVERONLY case ICEP_QWCLIENT: collection = cls.sockets; break; #endif #ifndef CLIENTONLY case ICEP_QWSERVER: collection = svs.sockets; break; #endif } if (!conname) { int rnd[2]; Sys_RandomBytes((void*)rnd, sizeof(rnd)); conname = va("fte%08x%08x", rnd[0], rnd[1]); } con = Z_Malloc(sizeof(*con)); con->conname = Z_StrDup(conname); con->friendlyname = Z_StrDup(peername); con->proto = proto; con->rpwd = Z_StrDup(""); con->rufrag = Z_StrDup(""); con->mode = mode; con->next = icelist; icelist = con; { int rnd[1]; //'must have at least 24 bits randomness' Sys_RandomBytes((void*)rnd, sizeof(rnd)); con->lufrag = Z_StrDup(va("%08x", rnd[0])); } { int rnd[4]; //'must have at least 128 bits randomness' Sys_RandomBytes((void*)rnd, sizeof(rnd)); con->lpwd = Z_StrDup(va("%08x%08x%08x%08x", rnd[0], rnd[1], rnd[2], rnd[3])); } Sys_RandomBytes((void*)&con->tiehigh, sizeof(con->tiehigh)); Sys_RandomBytes((void*)&con->tielow, sizeof(con->tielow)); if (collection) { int i; int adrno, adrcount=1; netadr_t adr; char adrbuf[MAX_ADR_SIZE]; int net = 0; for (i = 0; i < MAX_CONNECTIONS; i++) { if (!collection->conn[i]) continue; adrno = 0; if (collection->conn[i]->GetLocalAddress) { for (adrcount=1; (adrcount = collection->conn[i]->GetLocalAddress(collection->conn[i], &adr, adrno)) && adrno < adrcount; adrno++) { struct icecandidate_s *cand; int rnd[2]; if (adr.type == NA_IP || adr.type == NA_IPV6) { cand = Z_Malloc(sizeof(*cand)); cand->info.network = net; cand->info.port = ntohs(adr.port); adr.port = 0; //to make sure its not part of the string... Q_strncpyz(cand->info.addr, NET_AdrToString(adrbuf, sizeof(adrbuf), &adr), sizeof(cand->info.addr)); cand->info.generation = 0; cand->info.component = 1; cand->info.foundation = 1; cand->info.priority = (1<<24)*(126) + (1<<8)*((adr.type == NA_IP?32768:0)+net*256+(255-adrno)) + (1<<0)*(256 - cand->info.component); Sys_RandomBytes((void*)rnd, sizeof(rnd)); Q_strncpyz(cand->info.candidateid, va("x%08x%08x", rnd[0], rnd[1]), sizeof(cand->info.candidateid)); cand->dirty = true; cand->next = con->lc; con->lc = cand; } } } net++; } } return con; } #include "zlib.h" ftenet_connections_t *ICE_PickConnection(struct icestate_s *con) { switch(con->proto) { default: break; #ifndef SERVERONLY case ICEP_VOICE: case ICEP_QWCLIENT: return cls.sockets; #endif #ifndef CLIENTONLY case ICEP_QWSERVER: return svs.sockets; #endif } return NULL; } //if either remotecand is null, new packets will be sent to all. static qboolean ICE_SendSpam(struct icestate_s *con) { struct icecandidate_s *rc; int i; int bestlocal = -1; struct icecandidate_s *bestpeer = NULL; ftenet_connections_t *collection = ICE_PickConnection(con); if (!collection) return false; //only send one ping to each. for (i = 0; i < MAX_CONNECTIONS; i++) { if (collection->conn[i]) { for(rc = con->rc; rc; rc = rc->next) { if (!(rc->tried & (1u<tried & (1u<info.priority < rc->info.priority) { bestpeer = rc; bestlocal = i; } } } } } if (bestpeer && bestlocal >= 0) { netadr_t to; sizebuf_t buf; char data[512]; char integ[20]; int crc; qboolean usecandidate = false; memset(&buf, 0, sizeof(buf)); buf.maxsize = sizeof(data); buf.cursize = 0; buf.data = data; bestpeer->tried |= (1u<info.addr, bestpeer->info.port, &to)) return true; Con_DPrintf("Spam %i -> %s:%i\n", bestlocal, bestpeer->info.addr, bestpeer->info.port); if (!con->controlled && NET_CompareAdr(&to, &con->chosenpeer)) usecandidate = true; MSG_WriteShort(&buf, BigShort(0x0001)); MSG_WriteShort(&buf, 0); //fill in later MSG_WriteLong(&buf, BigLong(0x2112a442)); MSG_WriteLong(&buf, BigLong(0)); //randomid MSG_WriteLong(&buf, BigLong(0)); //randomid MSG_WriteLong(&buf, BigLong(0x80000000|bestlocal)); //randomid if (usecandidate) { MSG_WriteShort(&buf, BigShort(0x25));//ICE-USE-CANDIDATE MSG_WriteShort(&buf, BigShort(0)); } //username MSG_WriteShort(&buf, BigShort(0x6)); //USERNAME MSG_WriteShort(&buf, BigShort(strlen(con->rufrag) + 1 + strlen(con->lufrag))); SZ_Write(&buf, con->rufrag, strlen(con->rufrag)); MSG_WriteChar(&buf, ':'); SZ_Write(&buf, con->lufrag, strlen(con->lufrag)); while(buf.cursize&3) MSG_WriteChar(&buf, 0); //priority MSG_WriteShort(&buf, BigShort(0x24));//ICE-PRIORITY MSG_WriteShort(&buf, BigShort(4)); MSG_WriteLong(&buf, 0); //FIXME //these two attributes carry a random 64bit tie-breaker. //the controller is the one with the highest number. if (con->controlled) { MSG_WriteShort(&buf, BigShort(0x8029));//ICE-CONTROLLED MSG_WriteShort(&buf, BigShort(8)); MSG_WriteLong(&buf, BigLong(con->tiehigh)); MSG_WriteLong(&buf, BigLong(con->tielow)); } else { MSG_WriteShort(&buf, BigShort(0x802A));//ICE-CONTROLLING MSG_WriteShort(&buf, BigShort(8)); MSG_WriteLong(&buf, BigLong(con->tiehigh)); MSG_WriteLong(&buf, BigLong(con->tielow)); } //message integrity is a bit annoying data[2] = ((buf.cursize+4+sizeof(integ)-20)>>8)&0xff; //hashed header length is up to the end of the hmac attribute data[3] = ((buf.cursize+4+sizeof(integ)-20)>>0)&0xff; //but the hash is to the start of the attribute's header SHA1_HMAC(integ, sizeof(integ), con->rpwd, strlen(con->rpwd), data, buf.cursize); MSG_WriteShort(&buf, BigShort(0x8)); //MESSAGE-INTEGRITY MSG_WriteShort(&buf, BigShort(20)); //sha1 key length SZ_Write(&buf, integ, sizeof(integ)); //integrity data data[2] = ((buf.cursize+8-20)>>8)&0xff; //dummy length data[3] = ((buf.cursize+8-20)>>0)&0xff; crc = crc32(0, data, buf.cursize)^0x5354554e; MSG_WriteShort(&buf, BigShort(0x8028)); //FINGERPRINT MSG_WriteShort(&buf, BigShort(sizeof(crc))); MSG_WriteLong(&buf, BigLong(crc)); //fill in the length (for the fourth time, after filling in the integrity and fingerprint) data[2] = ((buf.cursize-20)>>8)&0xff; data[3] = ((buf.cursize-20)>>0)&0xff; collection->conn[bestlocal]->SendPacket(collection->conn[bestlocal], buf.cursize, data, &to); return true; } return false; } void ICE_ToStunServer(struct icestate_s *con) { sizebuf_t buf; char data[512]; int crc; ftenet_connections_t *collection = ICE_PickConnection(con); if (!collection) return; if (!con->stunrnd[0]) Sys_RandomBytes((char*)con->stunrnd, sizeof(con->stunrnd)); Con_DPrintf("Spam stun %s\n", NET_AdrToString(data, sizeof(data), &con->pubstunserver)); memset(&buf, 0, sizeof(buf)); buf.maxsize = sizeof(data); buf.cursize = 0; buf.data = data; MSG_WriteShort(&buf, BigShort(0x0001)); MSG_WriteShort(&buf, 0); //fill in later MSG_WriteLong(&buf, BigLong(0x2112a442)); MSG_WriteLong(&buf, BigLong(con->stunrnd[0])); //randomid MSG_WriteLong(&buf, BigLong(con->stunrnd[1])); //randomid MSG_WriteLong(&buf, BigLong(con->stunrnd[2])); //randomid data[2] = ((buf.cursize+8-20)>>8)&0xff; //dummy length data[3] = ((buf.cursize+8-20)>>0)&0xff; crc = crc32(0, data, buf.cursize)^0x5354554e; MSG_WriteShort(&buf, BigShort(0x8028)); //FINGERPRINT MSG_WriteShort(&buf, BigShort(sizeof(crc))); MSG_WriteLong(&buf, BigLong(crc)); //fill in the length (for the fourth time, after filling in the integrity and fingerprint) data[2] = ((buf.cursize-20)>>8)&0xff; data[3] = ((buf.cursize-20)>>0)&0xff; NET_SendPacket((con->proto==ICEP_QWSERVER)?NS_SERVER:NS_CLIENT, buf.cursize, data, &con->pubstunserver); } qboolean QDECL ICE_Set(struct icestate_s *con, char *prop, char *value) { if (!strcmp(prop, "state")) { int oldstate = con->state; if (!strcmp(value, STRINGIFY(ICE_CONNECTING))) con->state = ICE_CONNECTING; else if (!strcmp(value, STRINGIFY(ICE_INACTIVE))) con->state = ICE_INACTIVE; else if (!strcmp(value, STRINGIFY(ICE_FAILED))) con->state = ICE_FAILED; else if (!strcmp(value, STRINGIFY(ICE_CONNECTED))) con->state = ICE_CONNECTED; else { Con_Printf("ICE_Set invalid state %s\n", value); con->state = ICE_INACTIVE; } con->timeout = Sys_Milliseconds(); con->retries = 0; if (oldstate != con->state && con->state == ICE_CONNECTED) { if (con->chosenpeer.type == NA_INVALID) { con->state = ICE_FAILED; Con_Printf("ICE failed. peer not valid.\n"); } #ifndef SERVERONLY else if (con->proto == ICEP_QWCLIENT) { char msg[256]; // Con_Printf("Try typing connect %s\n", NET_AdrToString(msg, sizeof(msg), &con->chosenpeer)); Cbuf_AddText(va("\nconnect \"%s\"\n", NET_AdrToString(msg, sizeof(msg), &con->chosenpeer)), RESTRICT_LOCAL); } #endif #ifndef CLIENTONLY else if (con->proto == ICEP_QWSERVER) { extern void SVC_GetChallenge(); net_from = con->chosenpeer; SVC_GetChallenge(); } #endif if (con->state == ICE_CONNECTED) Con_Printf("%s connection established.\n", con->proto == ICEP_VOICE?"voice":"Quake"); } #if !defined(SERVERONLY) && defined(VOICECHAT) cl_voip_send.ival = (cl_voip_send.ival & ~4) | (NET_RTP_Active()?4:0); #endif } else if (!strcmp(prop, "controlled")) con->controlled = !!atoi(value); else if (!strcmp(prop, "controller")) con->controlled = !atoi(value); else if (!strncmp(prop, "codec", 5)) { int codec = atoi(prop+5); if (codec < 96 || codec > 127) return false; if (strcmp(value, "speex@8000") && strcmp(value, "speex@16000"))// && strcmp(value, "opus")) return false; codec -= 96; Z_Free(con->codec[codec]); con->codec[codec] = Z_StrDup(value); } else if (!strcmp(prop, "rufrag")) { Z_Free(con->rufrag); con->rufrag = Z_StrDup(value); } else if (!strcmp(prop, "rpwd")) { Z_Free(con->rpwd); con->rpwd = Z_StrDup(value); } else if (!strcmp(prop, "stunip")) { Z_Free(con->stunserver); con->stunserver = Z_StrDup(value); NET_StringToAdr(con->stunserver, con->stunport, &con->pubstunserver); } else if (!strcmp(prop, "stunport")) { con->stunport = atoi(value); if (con->stunserver) NET_StringToAdr(con->stunserver, con->stunport, &con->pubstunserver); } else return false; return true; } qboolean QDECL ICE_Get(struct icestate_s *con, char *prop, char *value, int valuelen) { if (!strcmp(prop, "sid")) Q_strncpyz(value, con->conname, valuelen); else if (!strcmp(prop, "state")) Q_snprintfz(value, valuelen, "%i", con->state); else if (!strcmp(prop, "lufrag")) Q_strncpyz(value, con->lufrag, valuelen); else if (!strcmp(prop, "lpwd")) Q_strncpyz(value, con->lpwd, valuelen); else if (!strncmp(prop, "codec", 5)) { int codec = atoi(prop+5); if (codec < 96 || codec > 127) return false; codec -= 96; if (con->codec[codec]) Q_strncpyz(value, con->codec[codec], valuelen); else Q_strncpyz(value, "", valuelen); } else if (!strcmp(prop, "newlc")) { struct icecandidate_s *can; Q_strncpyz(value, "0", valuelen); for (can = con->lc; can; can = can->next) { if (can->dirty) { Q_strncpyz(value, "1", valuelen); break; } } } else return false; return true; } struct icecandinfo_s *QDECL ICE_GetLCandidateInfo(struct icestate_s *con) { struct icecandidate_s *can; for (can = con->lc; can; can = can->next) { if (can->dirty) { can->dirty = false; return &can->info; } } return NULL; } void QDECL ICE_AddRCandidateInfo(struct icestate_s *con, struct icecandinfo_s *n) { struct icecandidate_s *o; qboolean isnew; netadr_t peer; //I don't give a damn about rtpc. if (n->component != 1) return; if (!NET_StringToAdr(n->addr, n->port, &peer)) return; for (o = con->rc; o; o = o->next) { //not sure that updating candidates is particuarly useful tbh, but hey. if (!strcmp(o->info.candidateid, n->candidateid)) break; } if (!o) { o = Z_Malloc(sizeof(*o)); o->next = con->rc; con->rc = o; Q_strncpyz(o->info.candidateid, n->candidateid, sizeof(o->info.candidateid)); isnew = true; } else { isnew = false; } Q_strncpyz(o->info.addr, n->addr, sizeof(o->info.addr)); o->info.port = n->port; o->info.type = n->type; o->info.priority = n->priority; o->info.network = n->network; o->info.generation = n->generation; o->info.foundation = n->foundation; o->info.component = n->component; o->info.transport = n->transport; o->dirty = true; o->peer = peer; o->tried = 0; o->reachable = 0; Con_DPrintf("%s remote candidate %s: [%s]:%i\n", isnew?"Added":"Updated", o->info.candidateid, o->info.addr, o->info.port); } static void ICE_Destroy(struct icestate_s *con) { //has already been unlinked Z_Free(con); } static void ICE_Tick(void) { struct icestate_s *con; unsigned int curtime = Sys_Milliseconds(); for (con = icelist; con; con = con->next) { switch(con->mode) { case ICEM_RAW: //raw doesn't do handshakes or keepalives. it should just directly connect. //raw just uses the first (assumed only) option if (con->state == ICE_CONNECTING) { struct icecandidate_s *rc; rc = con->rc; if (rc) NET_StringToAdr(rc->info.addr, rc->info.port, &con->chosenpeer); else con->chosenpeer.type = NA_INVALID; ICE_Set(con, "state", STRINGIFY(ICE_CONNECTED)); } break; case ICEM_ICE: if (con->state == ICE_CONNECTING) { if (con->stunretry < curtime && con->pubstunserver.type != NA_INVALID) { ICE_ToStunServer(con); con->stunretry = curtime + 2*1000; } if (con->keepalive < curtime) { if (!ICE_SendSpam(con)) { struct icecandidate_s *rc; struct icecandidate_s *best = NULL; for (rc = con->rc; rc; rc = rc->next) { if (rc->reachable && (!best || rc->info.priority > best->info.priority)) best = rc; } if (best) { best->tried = ~best->reachable; con->chosenpeer = best->peer; ICE_SendSpam(con); } else { for (rc = con->rc; rc; rc = rc->next) rc->tried = 0; } con->retries++; if (con->retries > 32) con->retries = 32; con->keepalive = curtime + 200*(con->retries); //RTO } else con->keepalive = curtime + 50*(con->retries+1); //Ta } } break; } } } void QDECL ICE_Close(struct icestate_s *con) { struct icestate_s **link; ICE_Set(con, "state", STRINGIFY(ICE_INACTIVE)); for (link = &icelist; *link; ) { if (con == *link) { *link = con->next; ICE_Destroy(con); return; } else link = &(*link)->next; } } void QDECL ICE_CloseModule(void *module) { struct icestate_s **link, *con; for (link = &icelist; *link; ) { con = *link; if (con->module == module) { *link = con->next; ICE_Destroy(con); } else link = &(*link)->next; } } icefuncs_t iceapi = { ICE_Create, ICE_Set, ICE_Get, ICE_GetLCandidateInfo, ICE_AddRCandidateInfo, ICE_Close, ICE_CloseModule }; static qboolean NET_WasStun(netsrc_t netsrc) { #if !defined(SERVERONLY) && defined(VOICECHAT) if (netsrc == NS_CLIENT) { if (NET_RTP_Parse()) return true; } #endif if ((net_from.type == NA_IP || net_from.type == NA_IPV6) && net_message.cursize >= 20) { stunhdr_t *stun = (stunhdr_t*)net_message.data; int stunlen = BigShort(stun->msglen); if ((stun->msgtype == BigShort(0x0101) || stun->msgtype == BigShort(0x0111)) && net_message.cursize == stunlen + sizeof(*stun)) { //binding reply (or error) netadr_t adr = net_from; char xor[16]; short portxor; stunattr_t *attr = (stunattr_t*)(stun+1); int alen; while(stunlen) { stunlen -= sizeof(*attr); alen = (unsigned short)BigShort(attr->attrlen); if (alen > stunlen) return false; stunlen -= alen; switch(BigShort(attr->attrtype)) { default: break; case 1: case 0x20: if (BigShort(attr->attrtype) == 0x20) { portxor = *(short*)&stun->magiccookie; memcpy(xor, &stun->magiccookie, sizeof(xor)); } else { portxor = 0; memset(xor, 0, sizeof(xor)); } if (alen == 8 && ((qbyte*)attr)[5] == 1) //ipv4 MAPPED-ADDRESS { char str[256]; adr.type = NA_IP; adr.port = (((short*)attr)[3]) ^ portxor; *(int*)adr.address.ip = *(int*)(&((qbyte*)attr)[8]) ^ *(int*)xor; NET_AdrToString(str, sizeof(str), &adr); } else if (alen == 20 && ((qbyte*)attr)[5] == 2) //ipv6 MAPPED-ADDRESS { netadr_t adr; char str[256]; adr.type = NA_IPV6; adr.port = (((short*)attr)[3]) ^ portxor; ((int*)adr.address.ip6)[0] = ((int*)&((qbyte*)attr)[8])[0] ^ ((int*)xor)[0]; ((int*)adr.address.ip6)[1] = ((int*)&((qbyte*)attr)[8])[1] ^ ((int*)xor)[1]; ((int*)adr.address.ip6)[2] = ((int*)&((qbyte*)attr)[8])[2] ^ ((int*)xor)[2]; ((int*)adr.address.ip6)[3] = ((int*)&((qbyte*)attr)[8])[3] ^ ((int*)xor)[3]; NET_AdrToString(str, sizeof(str), &adr); } { struct icestate_s *con; for (con = icelist; con; con = con->next) { char str[256]; struct icecandidate_s *rc; if (con->mode != ICEM_ICE) continue; //check to see if this is a new peer-reflexive address, which happens when the peer is behind a nat. if (NET_CompareAdr(&net_from, &con->pubstunserver)) { for (rc = con->lc; rc; rc = rc->next) { if (NET_CompareAdr(&adr, &rc->peer)) break; } if (!rc) { struct icecandidate_s *rc; rc = Z_Malloc(sizeof(*rc)); rc->next = con->lc; con->lc = rc; rc->peer = adr; NET_BaseAdrToString(rc->info.addr, sizeof(rc->info.addr), &adr); rc->info.port = ntohs(adr.port); rc->info.type = ICE_SRFLX; rc->info.component = 1; rc->dirty = true; rc->info.priority = 1; //FIXME Con_DPrintf("Public address: %s\n", str); } con->stunretry = Sys_Milliseconds() + 60*1000; } else { for (rc = con->rc; rc; rc = rc->next) { if (NET_CompareAdr(&net_from, &rc->peer)) { if (!(rc->reachable & (1u<<(net_from.connum-1)))) Con_DPrintf("We can reach %s\n", NET_AdrToString(str, sizeof(str), &net_from)); rc->reachable |= 1u<<(net_from.connum-1); if (NET_CompareAdr(&net_from, &con->chosenpeer) && (stun->transactid[2] & BigLong(0x80000000))) ICE_Set(con, "state", STRINGIFY(ICE_CONNECTED)); } } } } } break; case 9: { char msg[64]; char sender[256]; unsigned short len = BigShort(attr->attrlen)-4; if (len > sizeof(msg)-1) len = sizeof(msg)-1; memcpy(msg, &((qbyte*)attr)[8], len); msg[len] = 0; Con_DPrintf("%s: Stun error code %u : %s\n", NET_AdrToString(sender, sizeof(sender), &net_from), ((qbyte*)attr)[7], msg); if (((qbyte*)attr)[7] == 1) { //not authorised. } if (((qbyte*)attr)[7] == 87) { //role conflict. } } break; } alen = (alen+3)&~3; attr = (stunattr_t*)((char*)(attr+1) + alen); } return true; } else if (stun->msgtype == BigShort(0x0011) && net_message.cursize == stunlen + sizeof(*stun) && stun->magiccookie == BigLong(0x2112a442)) { //binding indication. used as an rtp keepalive. return true; } else if (stun->msgtype == BigShort(0x0001) && net_message.cursize == stunlen + sizeof(*stun) && stun->magiccookie == BigLong(0x2112a442)) { char username[256]; char integrity[20]; char *integritypos = NULL; int role = 0; struct icestate_s *con; unsigned int tiehigh = 0; unsigned int tielow = 0; qboolean usecandidate = false; int error = 0; unsigned int priority = 0; //binding request stunattr_t *attr = (stunattr_t*)(stun+1); int alen; *username = 0; while(stunlen) { alen = (unsigned short)BigShort(attr->attrlen); if (alen+sizeof(*attr) > stunlen) return false; switch((unsigned short)BigShort(attr->attrtype)) { default: //unknown attributes < 0x8000 are 'mandatory to parse', and such packets must be dropped in their entirety. //other ones are okay. if (!((unsigned short)BigShort(attr->attrtype) & 0x8000)) return false; break; case 0x6: //username if (alen < sizeof(username)) { memcpy(username, attr+1, alen); username[alen] = 0; // Con_Printf("Stun username = \"%s\"\n", username); } break; case 0x8: //message integrity memcpy(integrity, attr+1, sizeof(integrity)); integritypos = (char*)(attr+1); break; case 0x24: //priority // Con_Printf("priority = \"%i\"\n", priority); priority = BigLong(*(int*)(attr+1)); break; case 0x25: //USE-CANDIDATE usecandidate = true; break; case 0x8028: //fingerprint // Con_Printf("fingerprint = \"%08x\"\n", BigLong(*(int*)(attr+1))); break; case 0x8029://ice controlled case 0x802A://ice controlling role = (unsigned short)BigShort(attr->attrtype); //ice controlled tiehigh = BigLong(((int*)(attr+1))[0]); tielow = BigLong(((int*)(attr+1))[1]); break; } alen = (alen+3)&~3; attr = (stunattr_t*)((char*)(attr+1) + alen); stunlen -= alen+sizeof(*attr); } //we need to know which connection its from in order to validate the integrity for (con = icelist; con; con = con->next) { if (!strcmp(va("%s:%s", con->lufrag, con->rufrag), username)) break; } if (!con) { Con_DPrintf("Received STUN request from unknown user \"%s\"\n", username); } else { if (integritypos) { char key[20]; //the hmac is a bit weird. the header length includes the integrity attribute's length, but the checksum doesn't even consider the attribute header. stun->msglen = BigShort(integritypos+sizeof(integrity) - (char*)stun - sizeof(*stun)); SHA1_HMAC(key, sizeof(key), con->lpwd, strlen(con->lpwd), (qbyte*)stun, integritypos-4 - (char*)stun); if (memcmp(key, integrity, sizeof(integrity))) { Con_DPrintf("Integrity is bad! needed %x got %x\n", *(int*)key, *(int*)integrity); return true; } } if (con->state != ICE_INACTIVE) { sizebuf_t buf; char data[512]; int alen = 0, atype = 0, aofs = 0; int crc; struct icecandidate_s *rc; memset(&buf, 0, sizeof(buf)); buf.maxsize = sizeof(data); buf.cursize = 0; buf.data = data; //check to see if this is a new peer-reflexive address, which happens when the peer is behind a nat. for (rc = con->rc; rc; rc = rc->next) { if (NET_CompareAdr(&net_from, &rc->peer)) break; } if (!rc) { struct icecandidate_s *rc; rc = Z_Malloc(sizeof(*rc)); rc->next = con->rc; con->rc = rc; rc->peer = net_from; NET_BaseAdrToString(rc->info.addr, sizeof(rc->info.addr), &net_from); rc->info.port = ntohs(net_from.port); rc->info.type = ICE_PRFLX; rc->dirty = true; rc->info.priority = priority; } //flip ice control role, if we're wrong. if (role && role != (con->controlled?0x802A:0x8029)) { con->controlled = (tiehigh > con->tiehigh) || (tiehigh == con->tiehigh && tielow > con->tielow); Con_DPrintf("role conflict detected. We should be %s\n", con->controlled?"controlled":"controlling"); error = 87; } else if (usecandidate && con->controlled) { //in the controlled role, we're connected once we're told the pair to use (by the usecandidate flag). //note that this 'nominates' candidate pairs, from which the highest priority is chosen. //so we just pick select the highest. //this is problematic, however, as we don't actually know the real priority that the peer thinks we'll nominate it with. if (con->chosenpeer.type != NA_INVALID && !NET_CompareAdr(&net_from, &con->chosenpeer)) Con_DPrintf("Duplicate use-candidate\n"); con->chosenpeer = net_from; Con_DPrintf("use-candidate: %s\n", NET_AdrToString(data, sizeof(data), &net_from)); ICE_Set(con, "state", STRINGIFY(ICE_CONNECTED)); } if (net_from.type == NA_IP) { alen = 4; atype = 1; aofs = 0; } else if (net_from.type == NA_IPV6 && !*(int*)&net_from.address.ip6[0] && !*(int*)&net_from.address.ip6[4] && !*(short*)&net_from.address.ip6[8] && *(short*)&net_from.address.ip6[10] == (short)0xffff) { //just because we use an ipv6 address for ipv4 internally doesn't mean we should tell the peer that they're on ipv6... alen = 4; atype = 1; aofs = sizeof(net_from.address.ip6) - sizeof(net_from.address.ip); } else if (net_from.type == NA_IPV6) { alen = 16; atype = 2; aofs = 0; } MSG_WriteShort(&buf, BigShort(error?0x0111:0x0101)); MSG_WriteShort(&buf, BigShort(0)); //fill in later MSG_WriteLong(&buf, stun->magiccookie); MSG_WriteLong(&buf, stun->transactid[0]); MSG_WriteLong(&buf, stun->transactid[1]); MSG_WriteLong(&buf, stun->transactid[2]); if (error) { char *txt = "Role Conflict"; MSG_WriteShort(&buf, BigShort(0x0009)); MSG_WriteShort(&buf, BigShort(4 + strlen(txt))); MSG_WriteShort(&buf, 0); //reserved MSG_WriteByte(&buf, 0); //class MSG_WriteByte(&buf, error); //code SZ_Write(&buf, txt, strlen(txt)); //readable while(buf.cursize&3) //padding MSG_WriteChar(&buf, 0); } else if (1) { //xor mapped MSG_WriteShort(&buf, BigShort(0x0020)); MSG_WriteShort(&buf, BigShort(4+alen)); MSG_WriteShort(&buf, BigShort(atype)); MSG_WriteShort(&buf, net_from.port); SZ_Write(&buf, (char*)&net_from.address + aofs, alen); } else { //non-xor mapped MSG_WriteShort(&buf, BigShort(0x0001)); MSG_WriteShort(&buf, BigShort(4+alen)); MSG_WriteShort(&buf, BigShort(atype)); MSG_WriteShort(&buf, net_from.port); SZ_Write(&buf, (char*)&net_from.address + aofs, alen); } MSG_WriteShort(&buf, BigShort(0x6)); //USERNAME MSG_WriteShort(&buf, BigShort(strlen(username))); SZ_Write(&buf, username, strlen(username)); while(buf.cursize&3) MSG_WriteChar(&buf, 0); //message integrity is a bit annoying data[2] = ((buf.cursize+4+sizeof(integrity)-20)>>8)&0xff; //hashed header length is up to the end of the hmac attribute data[3] = ((buf.cursize+4+sizeof(integrity)-20)>>0)&0xff; //but the hash is to the start of the attribute's header SHA1_HMAC(integrity, sizeof(integrity), con->lpwd, strlen(con->lpwd), data, buf.cursize); MSG_WriteShort(&buf, BigShort(0x8)); //MESSAGE-INTEGRITY MSG_WriteShort(&buf, BigShort(sizeof(integrity))); //sha1 key length SZ_Write(&buf, integrity, sizeof(integrity)); //integrity data data[2] = ((buf.cursize+8-20)>>8)&0xff; //dummy length data[3] = ((buf.cursize+8-20)>>0)&0xff; crc = crc32(0, data, buf.cursize)^0x5354554e; MSG_WriteShort(&buf, BigShort(0x8028)); //FINGERPRINT MSG_WriteShort(&buf, BigShort(sizeof(crc))); MSG_WriteLong(&buf, BigLong(crc)); data[2] = ((buf.cursize-20)>>8)&0xff; data[3] = ((buf.cursize-20)>>0)&0xff; NET_SendPacket(netsrc, buf.cursize, data, &net_from); } } return true; } } return false; } #endif #ifndef CLIENTONLY void SVNET_AddPort_f(void) { char *s = Cmd_Argv(1); char *conname = Cmd_Argv(2); if (!*s && !*conname) { Con_Printf("Active Server ports:\n"); NET_PrintAddresses(svs.sockets); Con_Printf("end of list\n"); return; } if (!*conname) conname = NULL; //just in case if (!svs.sockets) { svs.sockets = FTENET_CreateCollection(true); #ifndef SERVERONLY FTENET_AddToCollection(svs.sockets, "SVLoopback", "27500", NA_LOOPBACK, true); #endif } FTENET_AddToCollection(svs.sockets, conname, *s?s:NULL, *s?NA_IP:NA_INVALID, true); } #endif #ifndef SERVERONLY void NET_ClientPort_f(void) { Con_Printf("Active Client ports:\n"); NET_PrintAddresses(cls.sockets); Con_Printf("end of list\n"); } #endif qboolean NET_WasSpecialPacket(netsrc_t netsrc) { ftenet_connections_t *collection = NULL; if (netsrc == NS_SERVER) { #ifndef CLIENTONLY collection = svs.sockets; #endif } else { #ifndef SERVERONLY collection = cls.sockets; #endif } if (NET_WasStun(netsrc)) return true; #ifdef HAVE_NATPMP if (NET_Was_NATPMP(collection)) return true; #endif return false; } /* ==================== NET_Init ==================== */ void NET_Init (void) { #ifdef _WIN32 int r; #ifdef IPPROTO_IPV6 HMODULE ws2_32dll; ws2_32dll = LoadLibrary("ws2_32.dll"); if (ws2_32dll) { pfreeaddrinfo = (void *)GetProcAddress(ws2_32dll, "freeaddrinfo"); pgetaddrinfo = (void *)GetProcAddress(ws2_32dll, "getaddrinfo"); if (!pgetaddrinfo || !pfreeaddrinfo) { pgetaddrinfo = NULL; pfreeaddrinfo = NULL; FreeLibrary(ws2_32dll); } } else pgetaddrinfo = NULL; #endif r = WSAStartup (MAKEWORD(1, 1), &winsockdata); if (r) Sys_Error ("Winsock initialization failed."); #endif Cvar_Register(&net_hybriddualstack, "networking"); Cvar_Register(&net_fakeloss, "networking"); #ifndef CLIENTONLY Cmd_AddCommand("sv_addport", SVNET_AddPort_f); #endif #ifndef SERVERONLY Cmd_AddCommand("cl_addport", NET_ClientPort_f); #endif } #ifndef SERVERONLY void NET_InitClient(void) { const char *port; int p; port = STRINGIFY(PORT_QWCLIENT); p = COM_CheckParm ("-clport"); if (p && p < com_argc) { port = com_argv[p+1]; } cls.sockets = FTENET_CreateCollection(false); #ifndef CLIENTONLY FTENET_AddToCollection(cls.sockets, "CLLoopback", "1", NA_LOOPBACK, true); #endif #ifdef HAVE_IPV4 FTENET_AddToCollection(cls.sockets, "CLUDP4", port, NA_IP, true); #endif #ifdef IPPROTO_IPV6 FTENET_AddToCollection(cls.sockets, "CLUDP6", port, NA_IPV6, true); #endif #ifdef USEIPX FTENET_AddToCollection(cls.sockets, "CLIPX", port, NA_IPX, true); #endif // // init the message buffer // net_message.maxsize = sizeof(net_message_buffer); net_message.data = net_message_buffer; Con_TPrintf(TL_CLIENTPORTINITED); } #endif #ifndef CLIENTONLY #ifdef HAVE_IPV4 void SV_Tcpport_Callback(struct cvar_s *var, char *oldvalue) { FTENET_AddToCollection(svs.sockets, var->name, var->string, NA_TCP, true); } cvar_t sv_port_tcp = CVARC("sv_port_tcp", "", SV_Tcpport_Callback); #endif #ifdef IPPROTO_IPV6 void SV_Tcpport6_Callback(struct cvar_s *var, char *oldvalue) { FTENET_AddToCollection(svs.sockets, var->name, var->string, NA_TCPV6, true); } cvar_t sv_port_tcp6 = CVARC("sv_port_tcp6", "", SV_Tcpport6_Callback); #endif #ifdef HAVE_IPV4 void SV_Port_Callback(struct cvar_s *var, char *oldvalue) { FTENET_AddToCollection(svs.sockets, var->name, var->string, NA_IP, true); } cvar_t sv_port_ipv4 = CVARC("sv_port", "27500", SV_Port_Callback); #endif #ifdef IPPROTO_IPV6 void SV_PortIPv6_Callback(struct cvar_s *var, char *oldvalue) { FTENET_AddToCollection(svs.sockets, var->name, var->string, NA_IPV6, true); } cvar_t sv_port_ipv6 = CVARC("sv_port_ipv6", "", SV_PortIPv6_Callback); #endif #ifdef USEIPX void SV_PortIPX_Callback(struct cvar_s *var, char *oldvalue) { FTENET_AddToCollection(svs.sockets, var->name, var->string, NA_IPX, true); } cvar_t sv_port_ipx = CVARC("sv_port_ipx", "", SV_PortIPX_Callback); #endif #ifdef HAVE_NATPMP void SV_Port_NatPMP_Callback(struct cvar_s *var, char *oldvalue) { FTENET_AddToCollection(svs.sockets, var->name, va("natpmp://%s", var->string), NA_NATPMP, true); } #if 1//def SERVERONLY #define NATPMP_DEFAULT_PORT "" //don't fuck with dedicated servers #else #define NATPMP_DEFAULT_PORT "5351" //home users, yay, lucky people. #endif cvar_t sv_port_natpmp = CVARCD("sv_port_natpmp", NATPMP_DEFAULT_PORT, SV_Port_NatPMP_Callback, "If set (typically to 5351), automatically configures your router's port forwarding. You can instead specify the full ip address of your router (192.168.1.1:5351 for example). Your router must have NAT-PMP supported and enabled."); #endif void SVNET_RegisterCvars(void) { int p; #if defined(TCPCONNECT) && defined(HAVE_IPV4) Cvar_Register (&sv_port_tcp, "networking"); sv_port_tcp.restriction = RESTRICT_MAX; #endif #if defined(TCPCONNECT) && defined(IPPROTO_IPV6) Cvar_Register (&sv_port_tcp6, "networking"); sv_port_tcp6.restriction = RESTRICT_MAX; #endif #ifdef IPPROTO_IPV6 Cvar_Register (&sv_port_ipv6, "networking"); sv_port_ipv6.restriction = RESTRICT_MAX; #endif #ifdef USEIPX Cvar_Register (&sv_port_ipx, "networking"); sv_port_ipx.restriction = RESTRICT_MAX; #endif #ifdef HAVE_IPV4 Cvar_Register (&sv_port_ipv4, "networking"); sv_port_ipv4.restriction = RESTRICT_MAX; #endif #ifdef HAVE_NATPMP Cvar_Register (&sv_port_natpmp, "networking"); sv_port_natpmp.restriction = RESTRICT_MAX; #endif // parse params for cvars p = COM_CheckParm ("-port"); if (!p) p = COM_CheckParm ("-svport"); if (p && p < com_argc) { int port = atoi(com_argv[p+1]); if (!port) port = PORT_QWSERVER; #ifdef HAVE_IPV4 if (*sv_port_ipv4.string) Cvar_SetValue(&sv_port_ipv4, port); #endif #ifdef IPPROTO_IPV6 if (*sv_port_ipv6.string) Cvar_SetValue(&sv_port_ipv6, port); #endif #ifdef USEIPX if (*sv_port_ipx.string) Cvar_SetValue(&sv_port_ipx, port); #endif } } void NET_CloseServer(void) { allowconnects = false; FTENET_CloseCollection(svs.sockets); svs.sockets = NULL; } void NET_InitServer(void) { if (sv_listen_nq.value || sv_listen_dp.value || sv_listen_qw.value || sv_listen_q3.value) { if (!svs.sockets) { svs.sockets = FTENET_CreateCollection(true); #ifndef SERVERONLY FTENET_AddToCollection(svs.sockets, "SVLoopback", STRINGIFY(PORT_QWSERVER), NA_LOOPBACK, true); #endif } allowconnects = true; #ifdef HAVE_IPV4 Cvar_ForceCallback(&sv_port_ipv4); #endif #ifdef IPPROTO_IPV6 Cvar_ForceCallback(&sv_port_ipv6); #endif #ifdef USEIPX Cvar_ForceCallback(&sv_port_ipx); #endif #if defined(TCPCONNECT) && defined(HAVE_TCP) Cvar_ForceCallback(&sv_port_tcp); #ifdef IPPROTO_IPV6 Cvar_ForceCallback(&sv_port_tcp6); #endif #endif #ifdef HAVE_NATPMP Cvar_ForceCallback(&sv_port_natpmp); #endif } else { NET_CloseServer(); #ifndef SERVERONLY svs.sockets = FTENET_CreateCollection(true); FTENET_AddToCollection(svs.sockets, "SVLoopback", STRINGIFY(PORT_QWSERVER), NA_LOOPBACK, true); #endif } // // init the message buffer // net_message.maxsize = sizeof(net_message_buffer); net_message.data = net_message_buffer; } #endif void NET_Tick(void) { #ifdef SUPPORT_ICE ICE_Tick(); #endif } /* ==================== NET_Shutdown ==================== */ void NET_Shutdown (void) { #ifndef CLIENTONLY NET_CloseServer(); #endif #ifndef SERVERONLY FTENET_CloseCollection(cls.sockets); cls.sockets = NULL; #endif #ifdef _WIN32 #ifdef SERVERTONLY if (!serverthreadID) //running as subsystem of client. Don't close all of it's sockets too. #endif WSACleanup (); #endif } #ifdef HAVE_TCP typedef struct { vfsfile_t funcs; SOCKET sock; qboolean conpending; char readbuffer[65536]; int readbuffered; char peer[1]; } tcpfile_t; void VFSTCP_Error(tcpfile_t *f) { if (f->sock != INVALID_SOCKET) { closesocket(f->sock); f->sock = INVALID_SOCKET; } } int QDECL VFSTCP_ReadBytes (struct vfsfile_s *file, void *buffer, int bytestoread) { tcpfile_t *tf = (tcpfile_t*)file; int len; int trying; if (tf->conpending) { fd_set wr; fd_set ex; struct timeval timeout; timeout.tv_sec = 0; timeout.tv_usec = 0; FD_ZERO(&wr); FD_SET(tf->sock, &wr); FD_ZERO(&ex); FD_SET(tf->sock, &ex); if (!select((int)tf->sock+1, NULL, &wr, &ex, &timeout)) return 0; tf->conpending = false; } if (tf->sock != INVALID_SOCKET) { trying = sizeof(tf->readbuffer) - tf->readbuffered; if (trying > 1500) trying = 1500; len = recv(tf->sock, tf->readbuffer + tf->readbuffered, trying, 0); if (len == -1) { int e = qerrno; if (e != EWOULDBLOCK) { switch(e) { case ENOTCONN: Con_Printf("connection to \"%s\" failed\n", tf->peer); break; case ECONNABORTED: Con_DPrintf("connection to \"%s\" aborted\n", tf->peer); break; case ECONNREFUSED: Con_DPrintf("connection to \"%s\" refused\n", tf->peer); break; case ECONNRESET: Con_DPrintf("connection to \"%s\" reset\n", tf->peer); break; default: Con_Printf("socket error %i\n", e); } VFSTCP_Error(tf); } //fixme: figure out wouldblock or error } else if (len == 0 && trying != 0) { //peer disconnected VFSTCP_Error(tf); } else { tf->readbuffered += len; } } //return a partially filled buffer. if (bytestoread > tf->readbuffered) bytestoread = tf->readbuffered; if (bytestoread < 0) VFSTCP_Error(tf); if (bytestoread > 0) { memcpy(buffer, tf->readbuffer, bytestoread); tf->readbuffered -= bytestoread; memmove(tf->readbuffer, tf->readbuffer+bytestoread, tf->readbuffered); return bytestoread; } else { if (tf->sock == INVALID_SOCKET) { perror("moo"); return -1; //signal an error } return 0; //signal nothing available } } int QDECL VFSTCP_WriteBytes (struct vfsfile_s *file, const void *buffer, int bytestoread) { tcpfile_t *tf = (tcpfile_t*)file; int len; if (tf->sock == INVALID_SOCKET) return 0; if (tf->conpending) { fd_set fd; struct timeval timeout; timeout.tv_sec = 0; timeout.tv_usec = 0; FD_ZERO(&fd); FD_SET(tf->sock, &fd); if (!select((int)tf->sock+1, NULL, &fd, NULL, &timeout)) return 0; tf->conpending = false; } len = send(tf->sock, buffer, bytestoread, 0); if (len == -1 || len == 0) { int e = qerrno; switch(e) { default: Sys_Printf("socket error %i\n", e); break; } // don't destroy it on write errors, because that prevents us from reading anything that was sent to us afterwards. // instead let the read handling kill it if there's nothing new to be read VFSTCP_ReadBytes(file, NULL, 0); return 0; } return len; } qboolean QDECL VFSTCP_Seek (struct vfsfile_s *file, unsigned long pos) { VFSTCP_Error((tcpfile_t*)file); return false; } unsigned long QDECL VFSTCP_Tell (struct vfsfile_s *file) { VFSTCP_Error((tcpfile_t*)file); return 0; } unsigned long QDECL VFSTCP_GetLen (struct vfsfile_s *file) { return 0; } void QDECL VFSTCP_Close (struct vfsfile_s *file) { VFSTCP_Error((tcpfile_t*)file); Z_Free(file); } vfsfile_t *FS_OpenTCP(const char *name, int defaultport) { tcpfile_t *newf; int sock; netadr_t adr = {0}; if (NET_StringToAdr(name, defaultport, &adr)) { sock = TCP_OpenStream(&adr); if (sock == INVALID_SOCKET) return NULL; newf = Z_Malloc(sizeof(*newf) + strlen(name)); strcpy(newf->peer, name); newf->conpending = true; newf->sock = sock; newf->funcs.Close = VFSTCP_Close; newf->funcs.Flush = NULL; newf->funcs.GetLen = VFSTCP_GetLen; newf->funcs.ReadBytes = VFSTCP_ReadBytes; newf->funcs.Seek = VFSTCP_Seek; newf->funcs.Tell = VFSTCP_Tell; newf->funcs.WriteBytes = VFSTCP_WriteBytes; newf->funcs.seekingisabadplan = true; return &newf->funcs; } else return NULL; } #elif 0 //defined(HAVE_WEBSOCKCL) This code is disabled. I cannot provide a reliable mechanism over chrome/nacls websockets at this time. Some module within the ppapi/nacl/chrome stack refuses to forward the data when stressed. All I can determine is that the connection has a gap. Hopefully this should be fixed by pepper_19. As far as Im aware, this and the relevent code in QTV should be functionally complete. typedef struct { vfsfile_t funcs; PP_Resource sock; unsigned char readbuffer[65536]; int readbuffered; qboolean havepacket; struct PP_Var incomingpacket; qboolean failed; } tcpfile_t; static void tcp_websocketgot(void *user_data, int32_t result) { tcpfile_t *wsc = user_data; if (result == PP_OK) { if (wsc->incomingpacket.type == PP_VARTYPE_UNDEFINED) { Con_Printf("ERROR: %s: var was not set by PPAPI. Data has been lost.\n", __func__); wsc->failed = true; } wsc->havepacket = true; } else { Sys_Printf("%s: %i\n", __func__, result); wsc->failed = true; } } static void tcp_websocketconnected(void *user_data, int32_t result) { tcpfile_t *wsc = user_data; if (result == PP_OK) { int res; //we got a successful connection, enable reception. struct PP_CompletionCallback ccb = {tcp_websocketgot, wsc, PP_COMPLETIONCALLBACK_FLAG_NONE}; res = ppb_websocket_interface->ReceiveMessage(wsc->sock, &wsc->incomingpacket, ccb); if (res != PP_OK_COMPLETIONPENDING) tcp_websocketgot(wsc, res); } else { Sys_Printf("%s: %i\n", __func__, result); //some sort of error connecting, make it timeout now wsc->failed = true; } } static void tcp_websocketclosed(void *user_data, int32_t result) { tcpfile_t *wsc = user_data; wsc->failed = true; if (wsc->havepacket) { wsc->havepacket = false; ppb_var_interface->Release(wsc->incomingpacket); } ppb_core->ReleaseResource(wsc->sock); wsc->sock = 0; // Z_Free(wsc); } void VFSTCP_Close (struct vfsfile_s *file) { /*meant to free the memory too, in this case we get the callback to do it*/ tcpfile_t *wsc = (void*)file; struct PP_CompletionCallback ccb = {tcp_websocketclosed, wsc, PP_COMPLETIONCALLBACK_FLAG_NONE}; ppb_websocket_interface->Close(wsc->sock, PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, PP_MakeUndefined(), ccb); } int VFSTCP_ReadBytes (struct vfsfile_s *file, void *buffer, int bytestoread) { tcpfile_t *wsc = (void*)file; int res; if (wsc->havepacket && wsc->readbuffered < bytestoread + 1024) { if (wsc->incomingpacket.type == PP_VARTYPE_UNDEFINED) Con_Printf("PPAPI bug: var is still undefined after being received\n"); else { int len = 0; unsigned char *utf8 = (unsigned char *)ppb_var_interface->VarToUtf8(wsc->incomingpacket, &len); unsigned char *out = (unsigned char *)wsc->readbuffer + wsc->readbuffered; wsc->havepacket = false; Con_Printf("Len: %i\n", len); while(len && out < wsc->readbuffer + sizeof(wsc->readbuffer)) { if ((*utf8 & 0xe0)==0xc0 && len > 1) { *out = ((utf8[0] & 0x1f)<<6) | ((utf8[1] & 0x3f)<<0); utf8+=2; len -= 2; } else if (*utf8 & 0x80) { *out = '?'; utf8++; len -= 1; } else { *out = utf8[0]; utf8++; len -= 1; } out++; } if (len) { Con_Printf("oh noes! buffer not big enough!\n"); wsc->failed = true; } Con_Printf("Old: %i\n", wsc->readbuffered); wsc->readbuffered = out - wsc->readbuffer; Con_Printf("New: %i\n", wsc->readbuffered); ppb_var_interface->Release(wsc->incomingpacket); wsc->incomingpacket = PP_MakeUndefined(); } if (!wsc->failed) { //get the next one struct PP_CompletionCallback ccb = {tcp_websocketgot, wsc, PP_COMPLETIONCALLBACK_FLAG_NONE}; res = ppb_websocket_interface->ReceiveMessage(wsc->sock, &wsc->incomingpacket, ccb); if (res != PP_OK_COMPLETIONPENDING) tcp_websocketgot(wsc, res); } } if (wsc->readbuffered) { // Con_Printf("Reading %i bytes of %i\n", bytestoread, wsc->readbuffered); if (bytestoread > wsc->readbuffered) bytestoread = wsc->readbuffered; memcpy(buffer, wsc->readbuffer, bytestoread); memmove(wsc->readbuffer, wsc->readbuffer+bytestoread, wsc->readbuffered-bytestoread); wsc->readbuffered -= bytestoread; } else if (wsc->failed) bytestoread = -1; /*signal eof*/ else bytestoread = 0; return bytestoread; } int VFSTCP_WriteBytes (struct vfsfile_s *file, const void *buffer, int bytestowrite) { tcpfile_t *wsc = (void*)file; int res; int outchars = 0; unsigned char outdata[bytestowrite*2+1]; unsigned char *out=outdata; const unsigned char *in=buffer; if (wsc->failed) return 0; for(res = 0; res < bytestowrite; res++) { /*FIXME: do we need this code?*/ if (!*in) { *out++ = 0xc0 | (0x100 >> 6); *out++ = 0x80 | (0x100 & 0x3f); } else if (*in >= 0x80) { *out++ = 0xc0 | (*in >> 6); *out++ = 0x80 | (*in & 0x3f); } else *out++ = *in; in++; outchars++; } *out = 0; struct PP_Var str = ppb_var_interface->VarFromUtf8(outdata, out - outdata); res = ppb_websocket_interface->SendMessage(wsc->sock, str); // Sys_Printf("FTENET_WebSocket_SendPacket: result %i\n", res); ppb_var_interface->Release(str); if (res == PP_OK) return bytestowrite; return 0; } qboolean VFSTCP_Seek (struct vfsfile_s *file, unsigned long pos) { //no seeking allowed tcpfile_t *wsc = (void*)file; Con_Printf("tcp seek?\n"); wsc->failed = true; return false; } unsigned long VFSTCP_Tell (struct vfsfile_s *file) { //no telling allowed tcpfile_t *wsc = (void*)file; Con_Printf("tcp tell?\n"); wsc->failed = true; return 0; } unsigned long VFSTCP_GetLen (struct vfsfile_s *file) { return 0; } /*nacl websockets implementation...*/ vfsfile_t *FS_OpenTCP(const char *name, int defaultport) { tcpfile_t *newf; netadr_t adr; if (!ppb_websocket_interface) { return NULL; } if (!NET_StringToAdr(name, defaultport, &adr)) return NULL; //couldn't resolve the name newf = Z_Malloc(sizeof(*newf)); if (newf) { struct PP_CompletionCallback ccb = {tcp_websocketconnected, newf, PP_COMPLETIONCALLBACK_FLAG_NONE}; newf->sock = ppb_websocket_interface->Create(pp_instance); struct PP_Var str = ppb_var_interface->VarFromUtf8(adr.address.websocketurl, strlen(adr.address.websocketurl)); ppb_websocket_interface->Connect(newf->sock, str, NULL, 0, ccb); ppb_var_interface->Release(str); newf->funcs.Close = VFSTCP_Close; newf->funcs.Flush = NULL; newf->funcs.GetLen = VFSTCP_GetLen; newf->funcs.ReadBytes = VFSTCP_ReadBytes; newf->funcs.Seek = VFSTCP_Seek; newf->funcs.Tell = VFSTCP_Tell; newf->funcs.WriteBytes = VFSTCP_WriteBytes; newf->funcs.seekingisabadplan = true; return &newf->funcs; } return NULL; } #else vfsfile_t *FS_OpenTCP(const char *name, int defaultport) { return NULL; } #endif