/* 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; int 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 = 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; 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); } } 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, "LocalHost"); break; #ifdef IRCCONNECT case NA_IRC: snprintf (s, len, "irc://%s", a.address.irc.user); 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 if (pgetaddrinfo) { 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, 0, &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; memset (a, 0, sizeof(*a)); a->type = NA_IRC; s+=6; 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); 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_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); static ftenet_generic_connection_t *FTENET_IPX_EstablishConnection(qboolean isserver, const char *address); static ftenet_generic_connection_t *FTENET_WebSocket_EstablishConnection(qboolean isserver, const char *address); 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; //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); } } } } #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; } qboolean FTENET_NATPMP_Close(struct ftenet_generic_connection_s *con) { //FIXME: we should send a packet to close the port Z_Free(con); return true; } 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; char adrs[MAX_ADR_SIZE]; int b; 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; #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 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 { int socketnum; int inlen; int outlen; enum { TCPC_UNKNOWN, TCPC_QIZMO, TCPC_WEBSOCKET } clienttype; char inbuffer[3000]; char outbuffer[3000]; 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; } int SHA1(char *digest, int maxdigestsize, char *string); 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) goto closesvstream; ret = recv(st->socketnum, st->inbuffer+st->inlen, sizeof(st->inbuffer)-st->inlen, 0); if (ret == 0) 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. //FIXME: reply with 426 Upgrade Required if wsversion is not supported if (!stricmp(arg[WCATTR_UPGRADE], "websocket") && !stricmp(arg[WCATTR_CONNECTION], "Upgrade")) { if (atoi(arg[WCATTR_WSVER]) != 13) { 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]; memmove(st->inbuffer, st->inbuffer+i, st->inlen - (i)); st->inlen -= i; tobase64(acceptkey, sizeof(acceptkey), sha1digest, SHA1(sha1digest, sizeof(sha1digest), va("%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11", arg[WCATTR_WSKEY]))); resp = va( "HTTP/1.1 101 Switching Protocols\r\n" "Upgrade: websocket\r\n" "Connection: Upgrade\r\n" "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 st->clienttype = TCPC_WEBSOCKET; } } 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" "" "" "" "