diff --git a/fteqtv/control.c b/fteqtv/control.c index 50b68604..5f0ccde8 100644 --- a/fteqtv/control.c +++ b/fteqtv/control.c @@ -509,6 +509,8 @@ int main(int argc, char **argv) cluster->qwdsocket[1] = INVALID_SOCKET; cluster->tcpsocket[0] = INVALID_SOCKET; cluster->tcpsocket[1] = INVALID_SOCKET; + cluster->anticheattime = 1*1000; + cluster->tooslowdelay = 100; cluster->qwlistenportnum = 0; cluster->allownqclients = true; strcpy(cluster->hostname, DEFAULT_HOSTNAME); @@ -526,12 +528,8 @@ int main(int argc, char **argv) if (cluster->qwdsocket[0] == INVALID_SOCKET && cluster->qwdsocket[1] == INVALID_SOCKET && !cluster->qwlistenportnum) { cluster->qwlistenportnum = 27599; - cluster->qwdsocket[0] = QW_InitUDPSocket(cluster->qwlistenportnum, false); - if (cluster->qwdsocket[0] != INVALID_SOCKET) - Sys_Printf(cluster, "opened udp4 port %i\n", cluster->qwlistenportnum); - cluster->qwdsocket[1] = QW_InitUDPSocket(cluster->qwlistenportnum, true); - if (cluster->qwdsocket[1] != INVALID_SOCKET) - Sys_Printf(cluster, "opened udp6 port %i\n", cluster->qwlistenportnum); + NET_InitUDPSocket(cluster, cluster->qwlistenportnum, true); + NET_InitUDPSocket(cluster, cluster->qwlistenportnum, false); } if (cluster->tcpsocket[0] == INVALID_SOCKET && cluster->tcpsocket[1] == INVALID_SOCKET && !cluster->tcplistenportnum) { diff --git a/fteqtv/dotnet2005/qtvprox.vcproj b/fteqtv/dotnet2005/qtvprox.vcproj index b3166d63..f0164236 100644 --- a/fteqtv/dotnet2005/qtvprox.vcproj +++ b/fteqtv/dotnet2005/qtvprox.vcproj @@ -43,8 +43,7 @@ + + diff --git a/fteqtv/forward.c b/fteqtv/forward.c index 9d840541..0ecef61c 100644 --- a/fteqtv/forward.c +++ b/fteqtv/forward.c @@ -119,7 +119,7 @@ void Fwd_ParseCommands(cluster_t *cluster, oproxy_t *prox) netmsg_t buf; int packetlength; int bytes; - bytes = recv(prox->sock, prox->inbuffer+prox->inbuffersize, sizeof(prox->inbuffer)-prox->inbuffersize, 0); + bytes = NET_WebSocketRecv(prox->sock, &prox->websocket, prox->inbuffer+prox->inbuffersize, sizeof(prox->inbuffer)-prox->inbuffersize, NULL); if (bytes < 0) { if (qerrno != EWOULDBLOCK && qerrno != EAGAIN) //not a problem, so long as we can flush it later. @@ -234,6 +234,67 @@ void Net_ProxySend(cluster_t *cluster, oproxy_t *prox, void *buffer, int length) { int wrap; + if (!length) + return; + + if (prox->websocket.websocket) + { + unsigned int c; + int enclen = 0; + + /*work out how much buffer space we'll need*/ + for (c = 0; c < length; c++) + { + if (((unsigned char*)buffer)[c] == 0 || ((unsigned char*)buffer)[c] >= 0x80) + enclen += 2; + else + enclen += 1; + } + + if (prox->buffersize-prox->bufferpos + (enclen+4) > MAX_PROXY_BUFFER) + { + Net_TryFlushProxyBuffer(cluster, prox); //try flushing + if (prox->buffersize-prox->bufferpos + (enclen+4) > MAX_PROXY_BUFFER) //damn, still too big. + { //they're too slow. hopefully it was just momentary lag + if (!prox->flushing) + { + printf("QTV client is too lagged\n"); + prox->flushing = true; + } + return; + } + } + + if (enclen >= 126) + { + prox->buffer[prox->buffersize++&(MAX_PROXY_BUFFER-1)] = 0x81; + prox->buffer[prox->buffersize++&(MAX_PROXY_BUFFER-1)] = 126; + prox->buffer[prox->buffersize++&(MAX_PROXY_BUFFER-1)] = enclen>>8; + prox->buffer[prox->buffersize++&(MAX_PROXY_BUFFER-1)] = enclen; + } + else + { + prox->buffer[prox->buffersize++&(MAX_PROXY_BUFFER-1)] = 0x81; + prox->buffer[prox->buffersize++&(MAX_PROXY_BUFFER-1)] = enclen; + } + while(length-->0) + { + c = *(unsigned char*)buffer; + buffer = (char*)buffer+1; + if (!c) + c |= 0x100; /*will get truncated at the other end*/ + if (c >= 0x80) + { + prox->buffer[prox->buffersize++&(MAX_PROXY_BUFFER-1)] = 0xc0 | (c>>6); + prox->buffer[prox->buffersize++&(MAX_PROXY_BUFFER-1)] = 0x80 | (c & 0x3f); + } + else + prox->buffer[prox->buffersize++&(MAX_PROXY_BUFFER-1)] = c; + } + Net_TryFlushProxyBuffer(cluster, prox); //try flushing in a desperate attempt to reduce bugs in google chrome. + return; + } + if (prox->buffersize-prox->bufferpos + length > MAX_PROXY_BUFFER) { Net_TryFlushProxyBuffer(cluster, prox); //try flushing @@ -622,22 +683,166 @@ void SV_ForwardStream(sv_t *qtv, void *buffer, int length) } } +/*wrapper around recv to handle websocket connections*/ +int NET_WebSocketRecv(SOCKET sock, wsrbuf_t *ws, unsigned char *out, unsigned int outlen, int *clen) +{ + unsigned int mask = 0; + unsigned int paylen; + int len, i; + + if (clen) + *clen = -1; + if (!ws->websocket) + return recv(sock, out, outlen, 0); + + if (!ws->wsbuflen) + { + len = recv(sock, ws->wsbuf, 2, 0); + if (len > 0) + ws->wsbuflen+=2; + else + return len; + } + if (ws->wsbuflen >= 2) + { + unsigned short ctrl = (ws->wsbuf[0]<<8) | ws->wsbuf[1]; + len = 2; + if ((ctrl & 0x7f) == 127) + len += 8; + else if ((ctrl & 0x7f) == 126) + len += 2; + if (ctrl & 0x80) + len += 4; + if (ws->wsbuflen < len) + { + paylen = recv(sock, ws->wsbuf+ws->wsbuflen, len - ws->wsbuflen, 0); + if (paylen > 0) + ws->wsbuflen += paylen; + else + return paylen; + } + + /*headers are complete*/ + if (ws->wsbuflen >= len) + { + if ((ctrl & 0x7f) == 127) + { +// paylen = paylen = ((ws->wsbuf[2]<<56) | (ws->wsbuf[3]<<48) | (ws->wsbuf[4]<<40) | (ws->wsbuf[5]<<32) | (ws->wsbuf[6]<<24) | (ws->wsbuf[7]<<16) | (ws->wsbuf[8]<<8) | (ws->wsbuf[9]<<0); +// if (paylen < 65536) + return 0; //error + } + else if ((ctrl & 0x7f) == 126) + { + paylen = (ws->wsbuf[2]<<8) | ws->wsbuf[3]; + if (paylen < 126) + return 0; //error + } + else + paylen = ctrl & 0x7f; + + if (ctrl & 0x80) + { + ((unsigned char*)&mask)[0] = ws->wsbuf[ws->wsbuflen-4]; + ((unsigned char*)&mask)[1] = ws->wsbuf[ws->wsbuflen-3]; + ((unsigned char*)&mask)[2] = ws->wsbuf[ws->wsbuflen-2]; + ((unsigned char*)&mask)[3] = ws->wsbuf[ws->wsbuflen-1]; + } + if (!(ctrl & 0x8000)) + return 0; //can't handle fragmented frames + + switch((ctrl>>8) & 0xf) + { + case 1: /*text frame*/ + len = 0; + while (outlen>len && ws->wspushed < paylen) + { + unsigned char n; + ctrl = recv(sock, &n, 1, 0); //FIXME: not my fastest code... + if (ctrl <= 0) + return len>0?len:ctrl; + + n ^= ((unsigned char*)&mask)[(ws->wspushed++)&3]; + + if (ws->wsbits) + { + *out++ = ((ws->wsbits&0x1f)<<6) | (n & 0x3f); + len++; + ws->wsbits = 0; + } + else if ((n & 0xe0) == 0xc0) + { + ws->wsbits = n; + } + else if (n & 0x80) + return 0; //error + else + { + *out++ = n; + len++; + } + } + if (ws->wspushed == paylen) + { + if (ws->wsbits) + return 0; //error + if (clen) + *clen = ws->wspushed; + ws->wspushed = 0; + ws->wsbuflen = 0; + } + return len; + case 2: /*binary frame*/ + if (outlen > paylen - ws->wspushed) + outlen = paylen - ws->wspushed; + len = recv(sock, out, outlen, 0); + if (len > 0) + { + for(i = 0; i < len; i++) + out[i] ^= ((unsigned char*)&mask)[(ws->wspushed+i)&3]; + ws->wspushed += len; + } + if (paylen == ws->wspushed) + { + /*success! move on to the next*/ + if (clen) + *clen = ws->wspushed; + ws->wspushed = 0; + ws->wsbuflen = 0; + } + return len; + case 8: /*close*/ + case 9: /*ping*/ + case 10: /*pong*/ + default: + return 0; //unsupported + } + return 0; + } + return 0; + } + else + return 0; +} + //returns true if the pending proxy should be unlinked //truth does not imply that it should be freed/released, just unlinked. qboolean SV_ReadPendingProxy(cluster_t *cluster, oproxy_t *pend) { + //drop: ignored if flushing is still set. clear flushing and set drop on errors. char tempbuf[512]; unsigned char *s; unsigned char *e; char *colon; float clientversion = 0; int len; + qboolean eoh; int headersize; qboolean raw; sv_t *qtv; - if (pend->drop) + if (pend->drop && !pend->flushing) { + printf("pending drop\n"); if (pend->srcfile) fclose(pend->srcfile); closesocket(pend->sock); @@ -672,6 +877,7 @@ qboolean SV_ReadPendingProxy(cluster_t *cluster, oproxy_t *pend) if (pend->bufferpos == pend->buffersize) { char buffer[MAX_PROXY_BUFFER/2]; + pend->droptime = cluster->curtime + 5*1000; len = fread(buffer, 1, sizeof(buffer), pend->srcfile); if (!len) { @@ -684,31 +890,32 @@ qboolean SV_ReadPendingProxy(cluster_t *cluster, oproxy_t *pend) return false; //don't try reading anything yet } - if (pend->bufferpos == pend->buffersize) - { - pend->drop = true; + if (pend->bufferpos != pend->buffersize) return false; - } - else + pend->flushing = false; + if (pend->drop) return false; } if (pend->droptime < cluster->curtime) { + printf("pending timeout\n"); pend->drop = true; + pend->flushing = false; return false; } len = sizeof(pend->inbuffer) - pend->inbuffersize - 1; - len = recv(pend->sock, pend->inbuffer+pend->inbuffersize, len, 0); + len = NET_WebSocketRecv(pend->sock, &pend->websocket, pend->inbuffer+pend->inbuffersize, len, NULL); if (len == 0) { pend->drop = true; return false; } + else if (len > 0) + pend->droptime = cluster->curtime + 5*1000; if (len < 0) { - return false; } @@ -717,6 +924,39 @@ qboolean SV_ReadPendingProxy(cluster_t *cluster, oproxy_t *pend) if (pend->inbuffersize >= 4) { + if (!strncmp(pend->inbuffer, "qizmo\n", 6)) + { + wsrbuf_t ws = pend->websocket; + SOCKET sock; + //carries unreliable packets + printf("tcpconnect\n"); + if (pend->srcfile) + fclose(pend->srcfile); + sock = pend->sock; + free(pend); + cluster->numproxies--; + + send(sock, "qizmo\n", 6, 0); + QW_TCPConnection(cluster, sock, ws); + + return true; + } + if (pend->websocket.websocket && !strncmp(pend->inbuffer, "\xff\xff\xff\xff", 4)) + { + wsrbuf_t ws = pend->websocket; + SOCKET sock; + //carries unreliable packets + printf("wsconnect\n"); + if (pend->srcfile) + fclose(pend->srcfile); + sock = pend->sock; + free(pend); + cluster->numproxies--; + + QW_TCPConnection(cluster, sock, ws); + + return true; + } if (ustrncmp(pend->inbuffer, "QTV\r", 4) && ustrncmp(pend->inbuffer, "QTV\n", 4) && ustrncmp(pend->inbuffer, "GET ", 4) && ustrncmp(pend->inbuffer, "POST ", 5)) { //I have no idea what the smeg you are. pend->drop = true; @@ -729,15 +969,25 @@ qboolean SV_ReadPendingProxy(cluster_t *cluster, oproxy_t *pend) //make sure there's a double \n somewhere - for (s = pend->inbuffer; sinbuffer+pend->inbuffersize; s++) + eoh = false; + for (s = pend->inbuffer; s<=pend->inbuffer+pend->inbuffersize; s++) { - if (s[0] == '\n' && (s[1] == '\n' || (s[1] == '\r' && s[2] == '\n'))) + if (s[0] == '\n' && s[1] == '\n') + { + s += 2; + eoh = true; break; + } + if (s[0] == '\n' && s[1] == '\r' && s[2] == '\n') + { + s += 3; + eoh = true; + break; + } } - if (!*s) + if (!eoh) return false; //don't have enough yet - s+=3; //Fixme: this is wrong - headersize = s - pend->inbuffer - 1; + headersize = s - pend->inbuffer; if (!ustrncmp(pend->inbuffer, "POST ", 5)) { @@ -747,9 +997,12 @@ qboolean SV_ReadPendingProxy(cluster_t *cluster, oproxy_t *pend) } else if (!ustrncmp(pend->inbuffer, "GET ", 4)) { + pend->drop = true; HTTPSV_GetMethod(cluster, pend); pend->flushing = true; - return false; + memmove(pend->inbuffer, pend->inbuffer+headersize, pend->inbuffersize-headersize); + pend->inbuffersize -= headersize; + return SV_ReadPendingProxy(cluster, pend); } raw = false; diff --git a/fteqtv/httpsv.c b/fteqtv/httpsv.c index e61a6e2f..7e821c00 100644 --- a/fteqtv/httpsv.c +++ b/fteqtv/httpsv.c @@ -4,7 +4,40 @@ //main reason to use connection close is because we're lazy and don't want to give sizes in advance (yes, we could use chunks..) +void tobase64(unsigned char *out, int outlen, unsigned char *in, int inlen) +{ + static 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; +} static const char qfont_table[256] = { '\0', '#', '#', '#', '#', '.', '#', '#', @@ -110,19 +143,20 @@ static void HTTPSV_SendHTTPHeader(cluster_t *cluster, oproxy_t *dest, char *erro if (nocache) { - s = "HTTP/1.1 %s OK\n" - "Content-Type: %s\n" - "Cache-Control: no-cache, must-revalidate\n" - "Expires: Mon, 26 Jul 1997 05:00:00 GMT\n" - "Connection: close\n" - "\n"; + s = "HTTP/1.1 %s OK\r\n" + "Content-Type: %s\r\n" + "Cache-Control: no-cache, must-revalidate\r\n" + "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n" + "Connection: close\r\n" + "\r\n"; } else { - s = "HTTP/1.1 %s OK\n" - "Content-Type: %s\n" - "Connection: close\n" - "\n"; + s = "HTTP/1.1 %s OK\r\n" + "Cache-Control: public, max-age=3600\r\n" + "Content-Type: %s\r\n" + "Connection: close\r\n" + "\r\n"; } snprintf(buffer, sizeof(buffer), s, error_code, content_type); @@ -635,7 +669,7 @@ static void HTTPSV_GenerateAdmin(cluster_t *cluster, oproxy_t *dest, int streami } if (o != result) { - strcpy(result, o); + strlcpy(result, o, sizeof(result)); o = result; } @@ -1077,6 +1111,9 @@ void HTTPSV_PostMethod(cluster_t *cluster, oproxy_t *pend, char *postdata) void HTTPSV_GetMethod(cluster_t *cluster, oproxy_t *pend) { + char connection[64]; + char upgrade[64]; + char *s; char *uri, *uriend; char *args; @@ -1090,6 +1127,48 @@ void HTTPSV_GetMethod(cluster_t *cluster, oproxy_t *pend) uriend = s; urilen = uriend - uri; + if (!pend->websocket.websocket && HTTPSV_GetHeaderField((char*)pend->inbuffer, "Connection", connection, sizeof(connection)) && !stricmp(connection, "Upgrade")) + { + if (HTTPSV_GetHeaderField((char*)pend->inbuffer, "Upgrade", upgrade, sizeof(upgrade)) && !stricmp(upgrade, "websocket")) + { + char ver[64]; + char key[64]; + HTTPSV_GetHeaderField((char*)pend->inbuffer, "Sec-WebSocket-Key", key, sizeof(key)); + + if (HTTPSV_GetHeaderField((char*)pend->inbuffer, "Sec-WebSocket-Version", ver, sizeof(ver)) && atoi(ver) != 13) + { + s = "HTTP/1.1 426 Upgrade Required\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n"; + Net_ProxySend(cluster, pend, s, strlen(s)); + return; + } + else + { + char acceptkey[20*2]; + unsigned char sha1digest[20]; + char padkey[512]; + snprintf(padkey, sizeof(padkey), "%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11", key); + tobase64(acceptkey, sizeof(acceptkey), sha1digest, SHA1(sha1digest, sizeof(sha1digest), padkey)); + + snprintf(padkey, sizeof(padkey), + "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: FTEQTVWebSocket\r\n" + "\r\n", acceptkey); + //send the websocket handshake response. + Net_ProxySend(cluster, pend, padkey, strlen(padkey)); + pend->websocket.websocket = true; + + printf("websocket upgrade\n"); + } + pend->drop = false; + return; + } + } + if (urimatch(uri, "/plugin.html", urilen)) { HTTPSV_GeneratePlugin(cluster, pend); diff --git a/fteqtv/netchan.c b/fteqtv/netchan.c index f338e3a4..8e67a320 100644 --- a/fteqtv/netchan.c +++ b/fteqtv/netchan.c @@ -21,13 +21,102 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "qtv.h" #include +#ifndef IPV6_V6ONLY + #define IPV6_V6ONLY 27 +#endif + #define curtime Sys_Milliseconds() +void NET_InitUDPSocket(cluster_t *cluster, int port, qboolean ipv6) +{ + int sock; + + int pf; + struct sockaddr *address; + struct sockaddr_in address4; + struct sockaddr_in6 address6; + int addrlen; + + unsigned long nonblocking = true; + unsigned long v6only = false; + +#pragma message("fixme") + if (ipv6) + { + pf = PF_INET6; + memset(&address6, 0, sizeof(address6)); + address6.sin6_family = AF_INET6; + address6.sin6_port = htons((u_short)port); + address = (struct sockaddr*)&address6; + addrlen = sizeof(address6); + } + else + { + pf = PF_INET; + address4.sin_family = AF_INET; + address4.sin_addr.s_addr = INADDR_ANY; + address4.sin_port = htons((u_short)port); + address = (struct sockaddr*)&address4; + addrlen = sizeof(address4); + } + + if (!ipv6 && !v6only && cluster->qwdsocket[1] != INVALID_SOCKET) + { + int sz = sizeof(v6only); + if (getsockopt(cluster->qwdsocket[1], IPPROTO_IPV6, IPV6_V6ONLY, (char *)&v6only, &sz) == 0 && !v6only) + port = 0; + } + + if (!port) + { + if (cluster->qwdsocket[ipv6] != INVALID_SOCKET) + { + closesocket(cluster->qwdsocket[ipv6]); + cluster->qwdsocket[ipv6] = INVALID_SOCKET; + Sys_Printf(cluster, "closed udp%i port\n", ipv6?6:4); + } + return; + } + + if ((sock = socket (pf, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET) + { + return; + } + + if (ioctlsocket (sock, FIONBIO, &nonblocking) == -1) + { + closesocket(sock); + return; + } + + if (pf == AF_INET6) + if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&v6only, sizeof(v6only)) == -1) + v6only = true; + + if (bind (sock, (void *)address, addrlen) == -1) + { + printf("socket bind error %i (%s)\n", qerrno, strerror(qerrno)); + closesocket(sock); + return; + } + + if (cluster->qwdsocket[ipv6] != INVALID_SOCKET) + { + closesocket(cluster->qwdsocket[ipv6]); + Sys_Printf(cluster, "closed udp%i port\n", ipv6?6:4); + } + cluster->qwdsocket[ipv6] = sock; + if (v6only) + Sys_Printf(cluster, "opened udp%i port %i\n", ipv6?6:4, port); + else + Sys_Printf(cluster, "opened udp port %i\n", port); +} + SOCKET NET_ChooseSocket(SOCKET sock[2], netadr_t *adr) { #ifdef AF_INET6 - if (((struct sockaddr *)adr)->sa_family == AF_INET6) + if (((struct sockaddr *)adr->sockaddr)->sa_family == AF_INET6) return sock[1]; #endif return sock[0]; @@ -36,8 +125,94 @@ SOCKET NET_ChooseSocket(SOCKET sock[2], netadr_t *adr) void NET_SendPacket(cluster_t *cluster, SOCKET sock, int length, void *data, netadr_t adr) { int ret; + int alen; - ret = sendto(sock, data, length, 0, (struct sockaddr *)&adr, sizeof(struct sockaddr_in)); +#ifdef AF_INET6 + if (((struct sockaddr *)&adr.sockaddr)->sa_family == AF_INET6) + alen = sizeof(struct sockaddr_in6); + else +#endif + alen = sizeof(struct sockaddr_in); + + if (adr.tcpcon) + { + tcpconnect_t *dest; + for (dest = cluster->tcpconnects; dest; dest = dest->next) + { + if (dest == adr.tcpcon) + break; + } + + if (dest) + { + int l; + + if (dest->websocket.websocket) + { + int enclen = 0, c; + for (c = 0; c < length; c++) + { + if (((unsigned char*)data)[c] == 0 || ((unsigned char*)data)[c] >= 0x80) + enclen += 2; + else + enclen += 1; + } + + if (dest->outbuffersize + 4+enclen < sizeof(dest->outbuffer)) + { + if (enclen >= 126) + { + dest->outbuffer[dest->outbuffersize++] = 0x81; + dest->outbuffer[dest->outbuffersize++] = 126; + dest->outbuffer[dest->outbuffersize++] = enclen>>8; + dest->outbuffer[dest->outbuffersize++] = enclen; + } + else + { + dest->outbuffer[dest->outbuffersize++] = 0x81; + dest->outbuffer[dest->outbuffersize++] = enclen; + } + while(length-->0) + { + c = *(unsigned char*)data; + data = (char*)data+1; + if (!c) + c |= 0x100; /*will get truncated at the other end*/ + if (c >= 0x80) + { + dest->outbuffer[dest->outbuffersize++] = 0xc0 | (c>>6); + dest->outbuffer[dest->outbuffersize++] = 0x80 | (c & 0x3f); + } + else + dest->outbuffer[dest->outbuffersize++] = c; + } + } + } + else + { + if (dest->outbuffersize + length < sizeof(dest->outbuffer)) + { + dest->outbuffer[dest->outbuffersize++] = length>>8; + dest->outbuffer[dest->outbuffersize++] = length&0xff; + memcpy(dest->outbuffer+dest->outbuffersize, data, length); + dest->outbuffersize += length; + } + } + + if (dest->outbuffersize) + { + l = send(dest->sock, dest->outbuffer, dest->outbuffersize, 0); + if (l > 0) + { + memmove(dest->outbuffer, dest->outbuffer+l, dest->outbuffersize-l); + dest->outbuffersize-=l; + } + } + } + return; + } + + ret = sendto(sock, data, length, 0, (struct sockaddr *)&adr.sockaddr, alen); if (ret < 0) { int er = qerrno; @@ -56,7 +231,7 @@ int Netchan_IsLocal (netadr_t adr) { case AF_INET: bytes = (unsigned char *)&((struct sockaddr_in *)&adr)->sin_addr; - if (bytes[0] == 127 && + if (bytes[0] == 127 && /*actualy, it should be only the first octet, but hey*/ bytes[1] == 0 && bytes[2] == 0 && bytes[3] == 1) @@ -177,7 +352,7 @@ Netchan_OutOfBandPrint Sends a text message in an out-of-band datagram ================ */ -void Netchan_OutOfBandPrint (cluster_t *cluster, SOCKET sock[], netadr_t adr, char *format, ...) +void Netchan_OutOfBandPrint (cluster_t *cluster, netadr_t adr, char *format, ...) { va_list argptr; char string[8192]; @@ -191,7 +366,7 @@ void Netchan_OutOfBandPrint (cluster_t *cluster, SOCKET sock[], netadr_t adr, ch #endif // _WIN32 va_end (argptr); - Netchan_OutOfBand (cluster, NET_ChooseSocket(sock, &adr), adr, strlen(string), (unsigned char *)string); + Netchan_OutOfBand (cluster, NET_ChooseSocket(cluster->qwdsocket, &adr), adr, strlen(string), (unsigned char *)string); } /* diff --git a/fteqtv/parse.c b/fteqtv/parse.c index 5a084503..eb72f66a 100644 --- a/fteqtv/parse.c +++ b/fteqtv/parse.c @@ -398,6 +398,21 @@ static void ParsePrint(sv_t *tv, netmsg_t *m, int to, unsigned int mask) if (level == 3) { + //FIXME: that number shouldn't be hard-coded + if (!strncmp(text, "#0:qtv_say:#", 12) || !strncmp(text, "#0:qtv_say_game:#", 17) || !strncmp(text, "#0:qtv_say_team_game:#", 22)) + { + char *colon; + colon = strchr(text, ':'); + colon = strchr(colon+1, ':'); + colon = strchr(colon+1, ':'); + if (colon) + { + //de-fuck qqshka's extra gibberish. + snprintf(buffer, sizeof(buffer), "%c%c[QTV]%s\n", svc_print, 3, colon+1); + Multicast(tv, buffer, strlen(buffer)+1, to, mask, QW|CONNECTING); + return; + } + } strcpy(buffer+2, text); buffer[1] = 1; } @@ -416,7 +431,7 @@ static void ParsePrint(sv_t *tv, netmsg_t *m, int to, unsigned int mask) } Multicast(tv, (char*)m->data+m->startpos, m->readpos - m->startpos, to, mask, QW|CONNECTING); -// Multicast(tv, buffer, strlen(buffer), to, mask, NQ); +// Multicast(tv, buffer, strlen(buffer)+1, to, mask, NQ); } static void ParseCenterprint(sv_t *tv, netmsg_t *m, int to, unsigned int mask) { @@ -562,6 +577,7 @@ static void ParsePlayerInfo(sv_t *tv, netmsg_t *m, qboolean clearoldplayers) for (i = 0; i < MAX_CLIENTS; i++) { //hide players //they'll be sent after this packet. + tv->map.players[i].oldactive = tv->map.players[i].active; tv->map.players[i].active = false; } } @@ -576,7 +592,6 @@ static void ParsePlayerInfo(sv_t *tv, netmsg_t *m, qboolean clearoldplayers) if (tv->usequakeworldprotocols) { - tv->map.players[num].old = tv->map.players[num].current; flags = (unsigned short)ReadShort (m); tv->map.players[num].current.origin[0] = ReadShort (m); diff --git a/fteqtv/qtv.h b/fteqtv/qtv.h index f7024596..bc82a321 100644 --- a/fteqtv/qtv.h +++ b/fteqtv/qtv.h @@ -224,7 +224,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #define ustrncmp(s1,s2,l) strncmp((char*)(s1),(char*)(s2),l) - +size_t strlcpy(char *dst, const char *src, size_t siz); #define VERSION "0.01" //this will be added to the serverinfo @@ -249,8 +249,10 @@ typedef int qboolean; extern "C" { #endif -typedef struct{ - char blob[64]; +typedef struct +{ + void *tcpcon; /*value not indirected, only compared*/ + char sockaddr[64]; } netadr_t; @@ -351,6 +353,7 @@ typedef struct { int leafcount; unsigned short leafs[MAX_ENTITY_LEAFS]; + qboolean oldactive:1; qboolean active:1; qboolean gibbed:1; qboolean dead:1; @@ -457,6 +460,15 @@ typedef struct viewer_s { int firstconnect; } viewer_t; +typedef struct +{ + qboolean websocket; //true if we need to use special handling + unsigned char wsbuf[16]; + int wsbuflen; + int wspushed; + int wsbits; +} wsrbuf_t; + //'other proxy', these are mvd stream clients. typedef struct oproxy_s { int authkey; @@ -471,6 +483,7 @@ typedef struct oproxy_s { FILE *srcfile; //buffer is padded with data from this file when its empty FILE *file; //recording a demo (written to) SOCKET sock; //playing to a proxy + wsrbuf_t websocket; unsigned char inbuffer[MAX_PROXY_INBUFFER]; unsigned int inbuffersize; //amount of data available. @@ -481,6 +494,20 @@ typedef struct oproxy_s { struct oproxy_s *next; } oproxy_t; +typedef struct tcpconnect_s +{ + struct tcpconnect_s *next; + SOCKET sock; + wsrbuf_t websocket; + netadr_t peeraddr; + + unsigned char inbuffer[MAX_PROXY_INBUFFER]; + unsigned int inbuffersize; //amount of data available. + + unsigned char outbuffer[MAX_PROXY_INBUFFER]; + unsigned int outbuffersize; //amount of data available. +} tcpconnect_t; + typedef struct { short origin[3]; unsigned char soundindex; @@ -679,6 +706,7 @@ typedef struct { struct cluster_s { SOCKET qwdsocket[2]; //udp + quakeworld protocols SOCKET tcpsocket[2]; //tcp listening socket (for mvd and listings and stuff) + tcpconnect_t *tcpconnects; //'tcpconnect' qizmo-compatible quakeworld-over-tcp connection char commandinput[512]; int inputlength; @@ -713,6 +741,8 @@ struct cluster_s { qboolean nobsp; qboolean allownqclients; //nq clients require no challenge qboolean nouserconnects; //prohibit users from connecting to new streams. + int anticheattime; //intial connection buffer delay (set high to block specing enemies) + int tooslowdelay; //if stream ran out of data, stop parsing for this long int maxviewers; @@ -801,7 +831,6 @@ void Broadcast(cluster_t *cluster, void *buffer, int length, int suitablefor); void ParseMessage(sv_t *tv, void *buffer, int length, int to, int mask); void BuildServerData(sv_t *tv, netmsg_t *msg, int servercount, viewer_t *spectatorflag); void BuildNQServerData(sv_t *tv, netmsg_t *msg, qboolean mvd, int servercount); -SOCKET QW_InitUDPSocket(int port, qboolean ipv6); void QW_UpdateUDPStuff(cluster_t *qtv); unsigned int Sys_Milliseconds(void); void Prox_SendInitialEnts(sv_t *qtv, oproxy_t *prox, netmsg_t *msg); @@ -820,8 +849,9 @@ void QTV_SayCommand(cluster_t *cluster, sv_t *qtv, viewer_t *v, char *fullcomman void PM_PlayerMove (pmove_t *pmove); void Netchan_Setup (SOCKET sock, netchan_t *chan, netadr_t adr, int qport, qboolean isclient); -void Netchan_OutOfBandPrint (cluster_t *cluster, SOCKET sock[], netadr_t adr, char *format, ...) PRINTFWARNING(4); +void Netchan_OutOfBandPrint (cluster_t *cluster, netadr_t adr, char *format, ...) PRINTFWARNING(4); int Netchan_IsLocal (netadr_t adr); +void NET_InitUDPSocket(cluster_t *cluster, int port, qboolean ipv6); void NET_SendPacket(cluster_t *cluster, SOCKET sock, int length, void *data, netadr_t adr); SOCKET NET_ChooseSocket(SOCKET sock[], netadr_t *adr); qboolean Net_CompareAddress(netadr_t *s1, netadr_t *s2, int qp1, int qp2); @@ -830,6 +860,7 @@ qboolean NQNetchan_Process(cluster_t *cluster, netchan_t *chan, netmsg_t *msg); void Netchan_Transmit (cluster_t *cluster, netchan_t *chan, int length, const void *data); void Netchan_OutOfBand (cluster_t *cluster, SOCKET sock, netadr_t adr, int length, void *data); qboolean Netchan_CanPacket (netchan_t *chan); +int NET_WebSocketRecv(SOCKET sock, wsrbuf_t *ws, unsigned char *out, unsigned int outlen, int *wslen); int SendList(sv_t *qtv, int first, const filename_t *list, int svc, netmsg_t *msg); int Prespawn(sv_t *qtv, int curmsgsize, netmsg_t *msg, int bufnum, int thisplayer); diff --git a/fteqtv/qw.c b/fteqtv/qw.c index bd13704a..d6f6d454 100644 --- a/fteqtv/qw.c +++ b/fteqtv/qw.c @@ -154,59 +154,7 @@ void WriteDeltaUsercmd (netmsg_t *m, const usercmd_t *from, usercmd_t *move) -SOCKET QW_InitUDPSocket(int port, qboolean ipv6) -{ - int sock; - int pf; - struct sockaddr *address; - struct sockaddr_in address4; - struct sockaddr_in6 address6; - int addrlen; - - unsigned long nonblocking = true; - -#pragma message("fixme") - if (ipv6) - { - pf = PF_INET6; - memset(&address6, 0, sizeof(address6)); - address6.sin6_family = AF_INET6; - address6.sin6_port = htons((u_short)port); - address = (struct sockaddr*)&address6; - addrlen = sizeof(address6); - } - else - { - pf = PF_INET; - address4.sin_family = AF_INET; - address4.sin_addr.s_addr = INADDR_ANY; - address4.sin_port = htons((u_short)port); - address = (struct sockaddr*)&address4; - addrlen = sizeof(address4); - } - - - - if ((sock = socket (pf, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET) - { - return INVALID_SOCKET; - } - - if (ioctlsocket (sock, FIONBIO, &nonblocking) == -1) - { - closesocket(sock); - return INVALID_SOCKET; - } - - if( bind (sock, (void *)address, addrlen) == -1) - { - printf("socket bind error %i (%s)\n", qerrno, strerror(qerrno)); - closesocket(sock); - return INVALID_SOCKET; - } - return sock; -} void BuildServerData(sv_t *tv, netmsg_t *msg, int servercount, viewer_t *viewer) { @@ -934,7 +882,7 @@ void NewQWClient(cluster_t *cluster, netadr_t *addr, char *connectmessage) if (!ChallengePasses(addr, atoi(challenge))) { - Netchan_OutOfBandPrint(cluster, cluster->qwdsocket, *addr, "n" "Bad challenge"); + Netchan_OutOfBandPrint(cluster, *addr, "n" "Bad challenge"); return; } @@ -942,7 +890,7 @@ void NewQWClient(cluster_t *cluster, netadr_t *addr, char *connectmessage) viewer = malloc(sizeof(viewer_t)); if (!viewer) { - Netchan_OutOfBandPrint(cluster, cluster->qwdsocket, *addr, "n" "Out of memory"); + Netchan_OutOfBandPrint(cluster, *addr, "n" "Out of memory"); return; } memset(viewer, 0, sizeof(viewer_t)); @@ -976,7 +924,7 @@ void NewQWClient(cluster_t *cluster, netadr_t *addr, char *connectmessage) strlcpy(viewer->userinfo, infostring, sizeof(viewer->userinfo)); ParseUserInfo(cluster, viewer); - Netchan_OutOfBandPrint(cluster, cluster->qwdsocket, *addr, "j"); + Netchan_OutOfBandPrint(cluster, *addr, "j"); NewClient(cluster, viewer); } @@ -1056,7 +1004,7 @@ void QTV_Rcon(cluster_t *cluster, char *message, netadr_t *from) if (!*cluster->adminpassword) { - Netchan_OutOfBandPrint(cluster, cluster->qwdsocket, *from, "n" "Bad rcon_password.\n"); + Netchan_OutOfBandPrint(cluster, *from, "n" "Bad rcon_password.\n"); return; } @@ -1067,11 +1015,11 @@ void QTV_Rcon(cluster_t *cluster, char *message, netadr_t *from) passlen = command-message; if (passlen != strlen(cluster->adminpassword) || strncmp(message, cluster->adminpassword, passlen)) { - Netchan_OutOfBandPrint(cluster, cluster->qwdsocket, *from, "n" "Bad rcon_password.\n"); + Netchan_OutOfBandPrint(cluster, *from, "n" "Bad rcon_password.\n"); return; } - Netchan_OutOfBandPrint(cluster, cluster->qwdsocket, *from, "n%s", Rcon_Command(cluster, NULL, command, buffer, sizeof(buffer), false)); + Netchan_OutOfBandPrint(cluster, *from, "n%s", Rcon_Command(cluster, NULL, command, buffer, sizeof(buffer), false)); } void QTV_Status(cluster_t *cluster, netadr_t *from) @@ -1287,13 +1235,13 @@ void ConnectionlessPacket(cluster_t *cluster, netadr_t *from, netmsg_t *m) if (!strncmp(buffer, "getchallenge", 12)) { i = NewChallenge(from); - Netchan_OutOfBandPrint(cluster, cluster->qwdsocket, *from, "c%i", i); + Netchan_OutOfBandPrint(cluster, *from, "c%i", i); return; } if (!strncmp(buffer, "connect 28 ", 11)) { if (cluster->numviewers >= cluster->maxviewers && cluster->maxviewers) - Netchan_OutOfBandPrint(cluster, cluster->qwdsocket, *from, "n" "Sorry, proxy is full.\n"); + Netchan_OutOfBandPrint(cluster, *from, "n" "Sorry, proxy is full.\n"); else NewQWClient(cluster, from, buffer); return; @@ -2032,7 +1980,7 @@ void SendPlayerStates(sv_t *tv, viewer_t *v, netmsg_t *msg) if (tv) { - if (tv->physicstime != v->settime && tv->cluster->chokeonnotupdated) + if (tv->physicstime != v->settime)// && tv->cluster->chokeonnotupdated) { WriteByte(msg, svc_updatestatlong); WriteByte(msg, STAT_TIME); @@ -2117,7 +2065,7 @@ void SendPlayerStates(sv_t *tv, viewer_t *v, netmsg_t *msg) WriteByte(msg, i); WriteShort(msg, flags); - if (!tv->map.players[i].active || + if (!tv->map.players[i].active || !tv->map.players[i].oldactive || (tv->map.players[i].current.origin[0] - tv->map.players[i].old.origin[0])*(tv->map.players[i].current.origin[0] - tv->map.players[i].old.origin[0]) > snapdist || (tv->map.players[i].current.origin[1] - tv->map.players[i].old.origin[1])*(tv->map.players[i].current.origin[1] - tv->map.players[i].old.origin[1]) > snapdist || (tv->map.players[i].current.origin[2] - tv->map.players[i].old.origin[2])*(tv->map.players[i].current.origin[2] - tv->map.players[i].old.origin[2]) > snapdist) @@ -2145,7 +2093,7 @@ void SendPlayerStates(sv_t *tv, viewer_t *v, netmsg_t *msg) } if (flags & PF_COMMAND) { - if (!tv->map.players[i].active) + if (!tv->map.players[i].active || !tv->map.players[i].oldactive) { to.angles[0] = tv->map.players[i].current.angles[0]; to.angles[1] = tv->map.players[i].current.angles[1]; @@ -2465,7 +2413,7 @@ void QTV_SayCommand(cluster_t *cluster, sv_t *qtv, viewer_t *v, char *fullcomman QW_PrintfToViewer(v, "Website: http://www.fteqw.com/\n" "Commands:\n" ".bind\n" - " Bind your keys to match Qizmo.\n" + " Bind your keys to drive the menu.\n" ".clients\n" " Lists the users connected to this\n" " proxy.\n" @@ -4176,19 +4124,204 @@ void SendViewerPackets(cluster_t *cluster, viewer_t *v) // else // printf("maynotsend (%i, %i)\n", cluster->curtime, v->nextpacket); } + +void QW_ProcessUDPPacket(cluster_t *cluster, netmsg_t *m, netadr_t from) +{ + char tempbuffer[256]; + int fromsize = sizeof(from); + int qport; + + viewer_t *v; + sv_t *useserver; + + if (*(int*)m->data == -1) + { //connectionless message + ConnectionlessPacket(cluster, &from, m); + return; + } + + if (m->cursize < 10) //otherwise it's a runt or bad. + { + qport = 0; + } + else + { + //read the qport + ReadLong(m); + ReadLong(m); + qport = ReadShort(m); + } + + for (v = cluster->viewers; v; v = v->next) + { + if (v->netchan.isnqprotocol) + { + if (Net_CompareAddress(&v->netchan.remote_address, &from, 0, 0)) + { + if (NQNetchan_Process(cluster, &v->netchan, m)) + { + useserver = v->server; + if (useserver && useserver->parsingconnectiondata) + useserver = NULL; + + v->timeout = cluster->curtime + 15*1000; + + ParseNQC(cluster, useserver, v, m); + + if (v->server && v->server->controller == v) + { + QTV_Run(v->server); + } + } + } + } + else + { + if (Net_CompareAddress(&v->netchan.remote_address, &from, v->netchan.qport, qport)) + { + if (v->server && v->server->controller == v && v->maysend) + SendViewerPackets(cluster, v); //do this before we read the new sequences + + if (Netchan_Process(&v->netchan, m)) + { + useserver = v->server; + if (useserver && useserver->parsingconnectiondata && useserver->controller != v) + useserver = NULL; + + v->timeout = cluster->curtime + 15*1000; + + if (v->server && v->server->controller == v) + { +// v->maysend = true; + v->server->maysend = true; + } + else + { + v->netchan.outgoing_sequence = v->netchan.incoming_sequence; //compensate for client->server packetloss. + if (!v->server) + v->maysend = true; + else if (!v->chokeme || !cluster->chokeonnotupdated) + { + v->maysend = true; + v->chokeme = cluster->chokeonnotupdated; + } + } + + ParseQWC(cluster, useserver, v, m); + + if (v->server && v->server->controller == v) + { + QTV_Run(v->server); + } + } + break; + } + } + } + if (!v && cluster->allownqclients) + { + unsigned int ctrl; + //NQ connectionless packet? + m->readpos = 0; + ctrl = ReadLong(m); + ctrl = SwapLong(ctrl); + if (ctrl & NETFLAG_CTL) + { //looks hopeful + switch(ReadByte(m)) + { + case CCREQ_SERVER_INFO: + ReadString(m, tempbuffer, sizeof(tempbuffer)); + if (!strcmp(tempbuffer, NET_GAMENAME_NQ)) + { + m->cursize = 0; + WriteLong(m, 0); + WriteByte(m, CCREP_SERVER_INFO); + WriteString(m, "??"); + WriteString(m, cluster->hostname); + WriteString(m, "Quake TV"); + WriteByte(m, cluster->numviewers>255?255:cluster->numviewers); + WriteByte(m, cluster->maxviewers>255?255:cluster->maxviewers); + WriteByte(m, NET_PROTOCOL_VERSION); + *(int*)m->data = BigLong(NETFLAG_CTL | m->cursize); + NET_SendPacket(cluster, NET_ChooseSocket(cluster->qwdsocket, &from), m->cursize, m->data, from); + } + break; + case CCREQ_CONNECT: + ReadString(m, tempbuffer, sizeof(tempbuffer)); + if (!strcmp(tempbuffer, NET_GAMENAME_NQ)) + { + if (ReadByte(m) == NET_PROTOCOL_VERSION) + { + //proquake extensions + int mod = ReadByte(m); + int modver = ReadByte(m); + int flags = ReadByte(m); + int passwd = ReadLong(m); + + //fte extension, sent so that dual-protocol servers will not create connections for dual-protocol clients + ReadString(m, tempbuffer, sizeof(tempbuffer)); + if (!strncmp(tempbuffer, "getchallenge", 12)) + break; + + //drop any old nq clients from this address + for (v = cluster->viewers; v; v = v->next) + { + if (v->netchan.isnqprotocol) + { + if (Net_CompareAddress(&v->netchan.remote_address, &from, 0, 0)) + { + Sys_Printf(cluster, "Dup connect from %s\n", v->name); + v->drop = true; + } + } + } + NewNQClient(cluster, &from); + } + } + break; + default: + break; + } + } + } +} + +void QW_TCPConnection(cluster_t *cluster, SOCKET sock, wsrbuf_t ws) +{ + int alen; + tcpconnect_t *tc; + tc = malloc(sizeof(*tc)); + if (!tc) + { + closesocket(sock); + return; + } + tc->sock = sock; + tc->websocket = ws; + tc->inbuffersize = 0; + tc->outbuffersize = 0; + + memset(&tc->peeraddr, 0, sizeof(tc->peeraddr)); + tc->peeraddr.tcpcon = tc; + + alen = sizeof(tc->peeraddr.sockaddr); + getpeername(sock, (struct sockaddr*)&tc->peeraddr.sockaddr, &alen); + + tc->next = cluster->tcpconnects; + cluster->tcpconnects = tc; +} + void QW_UpdateUDPStuff(cluster_t *cluster) { char buffer[MAX_MSGLEN]; //contains read info - char tempbuffer[256]; netadr_t from; - int fromsize = sizeof(from); + int fromsize = sizeof(from.sockaddr); int read; - int qport; netmsg_t m; int socketno; + tcpconnect_t *tc, **l; viewer_t *v, *f; - sv_t *useserver; if (*cluster->master && (cluster->curtime > cluster->mastersendtime || cluster->mastersendtime > cluster->curtime + 4*1000*60)) //urm... time wrapped? { @@ -4215,167 +4348,89 @@ void QW_UpdateUDPStuff(cluster_t *cluster) socketno++; if (socketno >= SOCKETGROUPS) break; + continue; + } + from.tcpcon = NULL; + read = recvfrom(cluster->qwdsocket[socketno], buffer, sizeof(buffer), 0, (struct sockaddr*)&from.sockaddr, (unsigned*)&fromsize); + + if (read < 0) //it's bad. + { + socketno++; + if (socketno >= SOCKETGROUPS) + break; + continue; } - read = recvfrom(cluster->qwdsocket[socketno], buffer, sizeof(buffer), 0, (struct sockaddr*)&from, (unsigned*)&fromsize); if (read <= 5) //otherwise it's a runt or bad. { - if (read < 0) //it's bad. - { - socketno++; - if (socketno >= SOCKETGROUPS) - break; - } continue; } m.cursize = read; + m.data = buffer; m.readpos = 0; - if (*(int*)buffer == -1) - { //connectionless message - ConnectionlessPacket(cluster, &from, &m); - continue; - } + QW_ProcessUDPPacket(cluster, &m, from); + } - if (read < 10) //otherwise it's a runt or bad. + for (l = &cluster->tcpconnects; *l; ) + { + int clen; + tc = *l; + read = sizeof(tc->inbuffer) - tc->inbuffersize; + read = NET_WebSocketRecv(tc->sock, &tc->websocket, tc->inbuffer+tc->inbuffersize, read, &clen); + if (read > 0) + tc->inbuffersize += read; + if (read == 0 || read < 0) { - if (read < 0) //it's bad. - break; + if (read == 0 || qerrno != EWOULDBLOCK) + { + *l = tc->next; + closesocket(tc->sock); + free(tc); + continue; + } + } + + if (clen >= 0) + { + /*if it really is a webclient connection, then the stream will be packetized already + so we don't waste extra space*/ + m.data = tc->inbuffer; + m.readpos = 0; + m.cursize = read; - qport = 0; + QW_ProcessUDPPacket(cluster, &m, tc->peeraddr); + + memmove(tc->inbuffer, tc->inbuffer+read, tc->inbuffersize - (read)); + tc->inbuffersize -= read; + continue; //ask to read the next packet } else { - //read the qport - ReadLong(&m); - ReadLong(&m); - qport = ReadShort(&m); - } - - for (v = cluster->viewers; v; v = v->next) - { - if (v->netchan.isnqprotocol) + while (tc->inbuffersize >= 2) { - if (Net_CompareAddress(&v->netchan.remote_address, &from, 0, 0)) + read = (tc->inbuffer[0]<<8) | tc->inbuffer[1]; + if (tc->inbuffersize >= 2+read) { - if (NQNetchan_Process(cluster, &v->netchan, &m)) - { - useserver = v->server; - if (useserver && useserver->parsingconnectiondata) - useserver = NULL; + m.data = tc->inbuffer+2; + m.readpos = 0; + m.cursize = read; - v->timeout = cluster->curtime + 15*1000; + QW_ProcessUDPPacket(cluster, &m, tc->peeraddr); - ParseNQC(cluster, useserver, v, &m); - - if (v->server && v->server->controller == v) - { - QTV_Run(v->server); - } - } + memmove(tc->inbuffer, tc->inbuffer+2+read, tc->inbuffersize - (2+read)); + tc->inbuffersize -= 2+read; } - } - else - { - if (Net_CompareAddress(&v->netchan.remote_address, &from, v->netchan.qport, qport)) - { - if (v->server && v->server->controller == v && v->maysend) - SendViewerPackets(cluster, v); //do this before we read the new sequences - - if (Netchan_Process(&v->netchan, &m)) - { - useserver = v->server; - if (useserver && useserver->parsingconnectiondata && useserver->controller != v) - useserver = NULL; - - v->timeout = cluster->curtime + 15*1000; - - if (v->server && v->server->controller == v) - { - // v->maysend = true; - v->server->maysend = true; - } - else - { - v->netchan.outgoing_sequence = v->netchan.incoming_sequence; //compensate for client->server packetloss. - if (!v->server) - v->maysend = true; - else if (!v->chokeme || !cluster->chokeonnotupdated) - { - v->maysend = true; - v->chokeme = cluster->chokeonnotupdated; - } - } - - ParseQWC(cluster, useserver, v, &m); - - if (v->server && v->server->controller == v) - { - QTV_Run(v->server); - } - } + else break; - } - } - } - if (!v && cluster->allownqclients) - { - //NQ connectionless packet? - m.readpos = 0; - read = ReadLong(&m); - read = SwapLong(read); - if (read & NETFLAG_CTL) - { //looks hopeful - switch(ReadByte(&m)) - { - case CCREQ_SERVER_INFO: - ReadString(&m, tempbuffer, sizeof(tempbuffer)); - if (!strcmp(tempbuffer, NET_GAMENAME_NQ)) - { - m.cursize = 0; - WriteLong(&m, 0); - WriteByte(&m, CCREP_SERVER_INFO); - WriteString(&m, "??"); - WriteString(&m, cluster->hostname); - WriteString(&m, "Quake TV"); - WriteByte(&m, cluster->numviewers>255?255:cluster->numviewers); - WriteByte(&m, cluster->maxviewers>255?255:cluster->maxviewers); - WriteByte(&m, NET_PROTOCOL_VERSION); - *(int*)m.data = BigLong(NETFLAG_CTL | m.cursize); - NET_SendPacket(cluster, NET_ChooseSocket(cluster->qwdsocket, &from), m.cursize, m.data, from); - } - break; - case CCREQ_CONNECT: - ReadString(&m, tempbuffer, sizeof(tempbuffer)); - if (!strcmp(tempbuffer, NET_GAMENAME_NQ)) - { - if (ReadByte(&m) == NET_PROTOCOL_VERSION) - { - //drop any old nq clients from this address - for (v = cluster->viewers; v; v = v->next) - { - if (v->netchan.isnqprotocol) - { - if (Net_CompareAddress(&v->netchan.remote_address, &from, 0, 0)) - { - Sys_Printf(cluster, "Dup connect from %s\n", v->name); - v->drop = true; - } - } - } - NewNQClient(cluster, &from); - } - } - break; - default: - break; - } } } + + + l = &(*l)->next; } - if (cluster->viewers && cluster->viewers->drop) { // Sys_Printf(cluster, "Dropping viewer %s\n", v->name); diff --git a/fteqtv/rcon.c b/fteqtv/rcon.c index 5e9b9b39..b4f77033 100644 --- a/fteqtv/rcon.c +++ b/fteqtv/rcon.c @@ -363,6 +363,7 @@ void Cmd_Hostname(cmdctxt_t *ctx) void Cmd_Master(cmdctxt_t *ctx) { + SOCKET s; char *newval = Cmd_Argv(ctx, 1); netadr_t addr; @@ -391,41 +392,17 @@ void Cmd_Master(cmdctxt_t *ctx) strlcpy(ctx->cluster->master, newval, sizeof(ctx->cluster->master)); ctx->cluster->mastersendtime = ctx->cluster->curtime; - if (ctx->cluster->qwdsocket != INVALID_SOCKET) - NET_SendPacket (ctx->cluster, NET_ChooseSocket(ctx->cluster->qwdsocket, &addr), 1, "k", addr); + s = NET_ChooseSocket(ctx->cluster->qwdsocket, &addr); + if (s != INVALID_SOCKET) + NET_SendPacket (ctx->cluster, s, 1, "k", addr); Cmd_Printf(ctx, "Master server set.\n"); } void Cmd_UDPPort(cmdctxt_t *ctx) { - int news; int newp = atoi(Cmd_Argv(ctx, 1)); - news = QW_InitUDPSocket(newp, false); - if (news != INVALID_SOCKET) - { - ctx->cluster->mastersendtime = ctx->cluster->curtime; - closesocket(ctx->cluster->qwdsocket[0]); - ctx->cluster->qwdsocket[0] = news; - ctx->cluster->qwlistenportnum = newp; - - Cmd_Printf(ctx, "Opened udp4 port %i (all connected qw clients will time out)\n", newp); - } - else - Cmd_Printf(ctx, "Failed to open udp4 port %i\n", newp); - - news = QW_InitUDPSocket(newp, true); - - if (news != INVALID_SOCKET) - { - ctx->cluster->mastersendtime = ctx->cluster->curtime; - closesocket(ctx->cluster->qwdsocket[1]); - ctx->cluster->qwdsocket[1] = news; - ctx->cluster->qwlistenportnum = newp; - - Cmd_Printf(ctx, "Opened udp6 port %i (all connected qw clients will time out)\n", newp); - } - else - Cmd_Printf(ctx, "Failed to open udp6 port %i\n", newp); + NET_InitUDPSocket(ctx->cluster, newp, true); + NET_InitUDPSocket(ctx->cluster, newp, false); } void Cmd_AdminPassword(cmdctxt_t *ctx) { @@ -622,11 +599,11 @@ void Cmd_Status(cmdctxt_t *ctx) Cmd_Printf(ctx, " Talking allowed\n"); if (ctx->cluster->nobsp) Cmd_Printf(ctx, " No BSP loading\n"); - if (ctx->cluster->tcpsocket != INVALID_SOCKET) + if (ctx->cluster->tcpsocket[0] != INVALID_SOCKET || ctx->cluster->tcpsocket[1] != INVALID_SOCKET) { Cmd_Printf(ctx, " tcp port %i\n", ctx->cluster->tcplistenportnum); } - if (ctx->cluster->tcpsocket != INVALID_SOCKET) + if (ctx->cluster->qwdsocket[0] != INVALID_SOCKET || ctx->cluster->qwdsocket[1] != INVALID_SOCKET) { Cmd_Printf(ctx, " udp port %i\n", ctx->cluster->qwlistenportnum); } @@ -780,6 +757,39 @@ void Cmd_AllowNQ(cmdctxt_t *ctx) Cmd_Printf(ctx, "allownq set\n"); } } + +void Cmd_InitialDelay(cmdctxt_t *ctx) +{ + char *val = Cmd_Argv(ctx, 1); + if (!*val) + { + Cmd_Printf(ctx, "initialdelay is currently %g seconds\n", ctx->cluster->anticheattime/1000.f); + } + else + { + ctx->cluster->anticheattime = atof(val)*1000; + if (ctx->cluster->anticheattime < 1) + ctx->cluster->anticheattime = 1; + Cmd_Printf(ctx, "initialdelay set\n"); + } +} + +void Cmd_SlowDelay(cmdctxt_t *ctx) +{ + char *val = Cmd_Argv(ctx, 1); + if (!*val) + { + Cmd_Printf(ctx, "slowdelay is currently %g seconds\n", ctx->cluster->tooslowdelay/1000.f); + } + else + { + ctx->cluster->tooslowdelay = atof(val)*1000; + if (ctx->cluster->tooslowdelay < 1) + ctx->cluster->tooslowdelay = 1; + Cmd_Printf(ctx, "slowdelay set\n"); + } +} + void Cmd_MaxProxies(cmdctxt_t *ctx) { char *val = Cmd_Argv(ctx, 1); @@ -1305,6 +1315,8 @@ const rconcommands_t rconcommands[] = {"exit", 0, 1, Cmd_Quit}, {"streams", 0, 1, Cmd_Streams, "shows a list of active streams"}, {"allownq", 0, 1, Cmd_AllowNQ, "permits nq clients to connect. This can be disabled as this code is less tested than the rest"}, + {"initialdelay",0, 1, Cmd_InitialDelay, "Specifies the duration for which new connections will be buffered. Large values prevents players from spectating their enemies as a cheap wallhack."}, + {"slowdelay", 0, 1, Cmd_SlowDelay, "If a server is not sending enough data, the proxy will delay parsing for this long."}, {"halt", 1, 0, Cmd_Halt, "disables a stream, preventing it from reconnecting until someone tries watching it anew. Boots current spectators"}, diff --git a/fteqtv/source.c b/fteqtv/source.c index c7fea4d7..49795a80 100644 --- a/fteqtv/source.c +++ b/fteqtv/source.c @@ -168,9 +168,10 @@ dblbreak: else #endif { //old fashioned method - ((struct sockaddr_in *)sadr)->sin_family = AF_INET; + struct sockaddr_in *sin = (struct sockaddr_in *)sadr->sockaddr; + sin->sin_family = AF_INET; - ((struct sockaddr_in *)sadr)->sin_port = htons(defaultport); + sin->sin_port = htons(defaultport); strcpy (copy, s); // strip off a trailing :port if present @@ -178,12 +179,12 @@ dblbreak: if (*colon == ':') { *colon = 0; - ((struct sockaddr_in *)sadr)->sin_port = htons((short)atoi(colon+1)); + sin->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); + *(int *)&sin->sin_addr = inet_addr(copy); } else { @@ -191,7 +192,7 @@ dblbreak: return 0; if (h->h_addrtype != AF_INET) return 0; - *(int *)&((struct sockaddr_in *)sadr)->sin_addr = *(int *)h->h_addr_list[0]; + *(int *)&sin->sin_addr = *(int *)h->h_addr_list[0]; } } @@ -200,16 +201,36 @@ dblbreak: qboolean Net_CompareAddress(netadr_t *s1, netadr_t *s2, int qp1, int qp2) { - struct sockaddr_in *i1=(void*)s1, *i2=(void*)s2; - if (i1->sin_family != i2->sin_family) + struct sockaddr *g1=(void*)s1->sockaddr, *g2=(void*)s2->sockaddr; + if (g1->sa_family != g2->sa_family) return false; - if (i1->sin_family == AF_INET) + switch(g1->sa_family) { - if (*(unsigned int*)&i1->sin_addr != *(unsigned int*)&i2->sin_addr) - return false; - if (i1->sin_port != i2->sin_port && qp1 != qp2) //allow qports to match instead of ports, if required. - return false; - return true; + case AF_INET: + { + struct sockaddr_in *i1=(void*)s1->sockaddr, *i2=(void*)s2->sockaddr; + if (*(unsigned int*)&i1->sin_addr != *(unsigned int*)&i2->sin_addr) + return false; + if (i1->sin_port != i2->sin_port && qp1 != qp2) //allow qports to match instead of ports, if required. + return false; + return true; + } + + case AF_INET6: + { + struct sockaddr_in6 *i1=(void*)s1->sockaddr, *i2=(void*)s2->sockaddr; + if (((unsigned int*)&i1->sin6_addr)[0] != ((unsigned int*)&i2->sin6_addr)[0]) + return false; + if (((unsigned int*)&i1->sin6_addr)[1] != ((unsigned int*)&i2->sin6_addr)[1]) + return false; + if (((unsigned int*)&i1->sin6_addr)[2] != ((unsigned int*)&i2->sin6_addr)[2]) + return false; + if (((unsigned int*)&i1->sin6_addr)[3] != ((unsigned int*)&i2->sin6_addr)[3]) + return false; + if (i1->sin6_port != i2->sin6_port && qp1 != qp2) //allow qports to match instead of ports, if required. + return false; + return true; + } } return false; } @@ -259,7 +280,7 @@ SOCKET Net_TCPListen(int port, qboolean ipv6) return INVALID_SOCKET; } - if( bind (sock, address, addrsize) == -1) + if (bind (sock, address, addrsize) == -1) { printf("socket bind error %i (%s)\n", qerrno, strerror(qerrno)); closesocket(sock); @@ -420,7 +441,7 @@ qboolean Net_ConnectToTCPServer(sv_t *qtv, char *ip) strcpy(qtv->status, "Unable to resolve server\n"); return false; } - afam = ((struct sockaddr*)&qtv->serveraddress)->sa_family; + afam = ((struct sockaddr*)&qtv->serveraddress.sockaddr)->sa_family; pfam = ((afam==AF_INET6)?PF_INET6:PF_INET); asz = ((afam==AF_INET6)?sizeof(struct sockaddr_in6):sizeof(struct sockaddr_in)); qtv->sourcesock = socket(pfam, SOCK_STREAM, IPPROTO_TCP); @@ -448,7 +469,7 @@ qboolean Net_ConnectToTCPServer(sv_t *qtv, char *ip) return false; } - if (connect(qtv->sourcesock, (struct sockaddr *)&qtv->serveraddress, asz) == INVALID_SOCKET) + if (connect(qtv->sourcesock, (struct sockaddr *)&qtv->serveraddress.sockaddr, asz) == INVALID_SOCKET) { err = qerrno; if (err != EINPROGRESS && err != EAGAIN && err != EWOULDBLOCK) //bsd sockets are meant to return EINPROGRESS, but some winsock drivers use EWOULDBLOCK instead. *sigh*... @@ -480,7 +501,7 @@ qboolean Net_ConnectToUDPServer(sv_t *qtv, char *ip) Sys_Printf(qtv->cluster, "Stream %i: Unable to resolve %s\n", qtv->streamid, ip); return false; } - afam = ((struct sockaddr*)&qtv->serveraddress)->sa_family; + afam = ((struct sockaddr*)&qtv->serveraddress.sockaddr)->sa_family; pfam = ((afam==AF_INET6)?PF_INET6:PF_INET); asz = ((afam==AF_INET6)?sizeof(struct sockaddr_in6):sizeof(struct sockaddr_in)); qtv->sourcesock = socket(pfam, SOCK_DGRAM, IPPROTO_UDP); @@ -899,8 +920,6 @@ qboolean Net_ReadStream(sv_t *qtv) return true; } -#define BUFFERTIME 10 //secords for artificial delay, so we can buffer things properly. - unsigned int Sys_Milliseconds(void) { #ifdef _WIN32 @@ -1100,7 +1119,7 @@ qboolean QTV_Connect(sv_t *qtv, char *serverurl) } else { - qtv->parsetime = Sys_Milliseconds() + BUFFERTIME*1000; + qtv->parsetime = Sys_Milliseconds() + qtv->cluster->anticheattime; } return true; } @@ -1379,11 +1398,12 @@ void QTV_ParseQWStream(sv_t *qtv) unsigned int fromlen; int readlen; netmsg_t msg; - fromlen = sizeof(from); //bug: this won't work on (free)bsd + fromlen = sizeof(from.sockaddr); //bug: this won't work on (free)bsd for (;;) { - readlen = recvfrom(qtv->sourcesock, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&from, &fromlen); + from.tcpcon = NULL; + readlen = recvfrom(qtv->sourcesock, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&from.sockaddr, &fromlen); if (readlen < 0) { //FIXME: Check for error @@ -2015,9 +2035,9 @@ void QTV_Run(sv_t *qtv) return; } - qtv->parsetime = Sys_Milliseconds() + BUFFERTIME*1000; + qtv->parsetime = Sys_Milliseconds() + qtv->cluster->anticheattime; if (!qtv->usequakeworldprotocols) - Sys_Printf(qtv->cluster, "Stream %i: Connection established, buffering for %i seconds\n", qtv->streamid, BUFFERTIME); + Sys_Printf(qtv->cluster, "Stream %i: Connection established, buffering for %g seconds\n", qtv->streamid, qtv->cluster->anticheattime/1000.0f); SV_ForwardStream(qtv, qtv->buffer, qtv->forwardpoint); } @@ -2030,9 +2050,9 @@ void QTV_Run(sv_t *qtv) { //not enough stuff to play. if (qtv->parsetime < qtv->curtime) { - qtv->parsetime = qtv->curtime + 2*1000; //add two seconds - if (qtv->sourcefile || qtv->sourcesock != INVALID_SOCKET) - QTV_Printf(qtv, "Stream %i: Not enough buffered\n", qtv->streamid); + qtv->parsetime = qtv->curtime + qtv->cluster->tooslowdelay; +// if (qtv->sourcefile || qtv->sourcesock != INVALID_SOCKET) +// QTV_Printf(qtv, "Stream %i: Not enough buffered\n", qtv->streamid); } break; } @@ -2045,9 +2065,9 @@ void QTV_Run(sv_t *qtv) length = 10; if (qtv->buffersize < length) { //not enough stuff to play. - qtv->parsetime = qtv->curtime + 2*1000; //add two seconds - if (qtv->sourcefile || qtv->sourcesock != INVALID_SOCKET) - QTV_Printf(qtv, "Stream %i: Not enough buffered\n", qtv->streamid); + qtv->parsetime = qtv->curtime + qtv->cluster->tooslowdelay; +// if (qtv->sourcefile || qtv->sourcesock != INVALID_SOCKET) +// QTV_Printf(qtv, "Stream %i: Not enough buffered\n", qtv->streamid); continue; } qtv->parsetime += buffer[0]; //well this was pointless @@ -2072,9 +2092,9 @@ void QTV_Run(sv_t *qtv) if (qtv->buffersize < lengthofs+4) { //the size parameter doesn't fit. - if (qtv->sourcefile || qtv->sourcesock != INVALID_SOCKET) - QTV_Printf(qtv, "Stream %i: Not enough buffered\n", qtv->streamid); - qtv->parsetime = qtv->curtime + 2*1000; //add two seconds +// if (qtv->sourcefile || qtv->sourcesock != INVALID_SOCKET) +// QTV_Printf(qtv, "Stream %i: Not enough buffered\n", qtv->streamid); + qtv->parsetime = qtv->curtime + qtv->cluster->tooslowdelay; break; } @@ -2101,9 +2121,9 @@ void QTV_Run(sv_t *qtv) if (length+lengthofs+4 > qtv->buffersize) { - if (qtv->sourcefile || qtv->sourcesock != INVALID_SOCKET) - QTV_Printf(qtv, "Stream %i: Not enough buffered\n", qtv->streamid); - qtv->parsetime = qtv->curtime + 2*1000; //add two seconds +// if (qtv->sourcefile || qtv->sourcesock != INVALID_SOCKET) +// QTV_Printf(qtv, "Stream %i: Not enough buffered\n", qtv->streamid); + qtv->parsetime = qtv->curtime + qtv->cluster->tooslowdelay; //add two seconds break; //can't parse it yet. }