From e2081b565f1f0eea4ac9e244086940f6612ffe92 Mon Sep 17 00:00:00 2001 From: Spoike Date: Mon, 9 Dec 2013 01:18:27 +0000 Subject: [PATCH] webquake-compatible websocket support. yuck. git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@4563 fc73d0e0-1445-4013-8a0c-d673dee63da5 --- engine/common/net_chan.c | 12 +++++- engine/common/net_wins.c | 83 ++++++++++++++++++++++++++++++++++------ engine/server/sv_main.c | 4 +- engine/server/sv_user.c | 3 +- 4 files changed, 88 insertions(+), 14 deletions(-) diff --git a/engine/common/net_chan.c b/engine/common/net_chan.c index 25c1ae9aa..e84e9172f 100644 --- a/engine/common/net_chan.c +++ b/engine/common/net_chan.c @@ -553,6 +553,16 @@ int Netchan_Transmit (netchan_t *chan, int length, qbyte *data, int rate) send.maxsize = MAX_NQMSGLEN + PACKET_HEADER; send.cursize = 0; + if (chan->remote_address.type == NA_TCP && chan->reliable_length) + { + //if over tcp, everything is assumed to be reliable. pretend it got acked. + chan->reliable_length = 0; //they got the entire message + chan->reliable_start = 0; + chan->incoming_reliable_acknowledged = chan->reliable_sequence; + chan->reliable_sequence++; + chan->nqreliable_allowed = true; + } + /*unreliables flood out, but reliables are tied to server sequences*/ if (chan->nqreliable_resendtime < realtime) chan->nqreliable_allowed = true; @@ -572,7 +582,7 @@ int Netchan_Transmit (netchan_t *chan, int length, qbyte *data, int rate) { MSG_WriteLong(&send, 0); MSG_WriteLong(&send, LongSwap(chan->reliable_sequence)); - if (i > MAX_NQDATAGRAM) + if (i > MAX_NQDATAGRAM && chan->remote_address.type != NA_TCP) i = MAX_NQDATAGRAM; SZ_Write (&send, chan->reliable_buf+chan->reliable_start, i); diff --git a/engine/common/net_wins.c b/engine/common/net_wins.c index 1ba87b582..fd77882f3 100644 --- a/engine/common/net_wins.c +++ b/engine/common/net_wins.c @@ -1572,6 +1572,14 @@ qboolean FTENET_Loop_GetPacket(ftenet_generic_connection_t *con) return NET_GetLoopPacket(con->thesocket, &net_from, &net_message); } +#ifdef HAVE_PACKET +//just a null function so we don't pass bad things to select. +int FTENET_Loop_SetReceiveFDSet(ftenet_generic_connection_t *gcon, fd_set *fdset) +{ + return 0; +} +#endif + qboolean FTENET_Loop_SendPacket(ftenet_generic_connection_t *con, int length, void *data, netadr_t *to) { if (to->type == NA_LOOPBACK) @@ -1609,6 +1617,9 @@ static ftenet_generic_connection_t *FTENET_Loop_EstablishConnection(qboolean iss newcon->GetPacket = FTENET_Loop_GetPacket; newcon->SendPacket = FTENET_Loop_SendPacket; newcon->Close = FTENET_Loop_Close; +#ifdef HAVE_PACKET + newcon->SetReceiveFDSet = FTENET_Loop_SetReceiveFDSet; +#endif newcon->islisten = isserver; newcon->addrtype[0] = NA_LOOPBACK; @@ -2605,6 +2616,7 @@ typedef struct ftenet_tcpconnect_stream_s { TCPC_QIZMO, //'qizmo\n' handshake, followed by packets prefixed with a 16bit packet length. TCPC_WEBSOCKETU, //utf-8 encoded data. TCPC_WEBSOCKETB, //binary encoded data (subprotocol = 'binary') + TCPC_WEBSOCKETNQ, //raw nq msg buffers with no encapsulation or handshake } clienttype; char inbuffer[3000]; char outbuffer[3000]; @@ -2612,6 +2624,8 @@ typedef struct ftenet_tcpconnect_stream_s { float timeouttime; netadr_t remoteaddr; struct ftenet_tcpconnect_stream_s *next; + + int fakesequence; //TCPC_WEBSOCKETNQ } ftenet_tcpconnect_stream_t; typedef struct { @@ -2896,29 +2910,56 @@ closesvstream: char acceptkey[20*2]; unsigned char sha1digest[20]; char *blurgh; + char *protoname = ""; memmove(st->inbuffer, st->inbuffer+i, st->inlen - (i)); st->inlen -= i; blurgh = va("%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11", arg[WCATTR_WSKEY]); tobase64(acceptkey, sizeof(acceptkey), sha1digest, SHA1(sha1digest, sizeof(sha1digest), blurgh, strlen(blurgh))); - Con_Printf("Websocket request for %s from %s\n", arg[WCATTR_URL], NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); + Con_Printf("Websocket request for %s from %s (%s)\n", arg[WCATTR_URL], NET_AdrToString (adr, sizeof(adr), &st->remoteaddr), arg[WCATTR_WSPROTO]); - resp = va( "HTTP/1.1 101 WebSocket Protocol Handshak\r\n" + if (!strcmp(arg[WCATTR_WSPROTO], "quake")) + { + st->clienttype = TCPC_WEBSOCKETNQ; //emscripten doesn't give us a choice, but its compact. + protoname = "Sec-WebSocket-Protocol: quake\r\n"; + } + else if (!strcmp(arg[WCATTR_WSPROTO], "binary")) + { + st->clienttype = TCPC_WEBSOCKETB; //emscripten doesn't give us a choice, but its compact. + protoname = "Sec-WebSocket-Protocol: binary\r\n"; //emscripten is a bit limited + } + else + { + st->clienttype = TCPC_WEBSOCKETU; //nacl supports only utf-8 encoded data, at least at the time I implemented it. + protoname = va("Sec-WebSocket-Protocol: %s\r\n", arg[WCATTR_WSPROTO]); //emscripten is a bit limited + } + + resp = va( "HTTP/1.1 101 WebSocket Protocol Handshake\r\n" "Upgrade: websocket\r\n" "Connection: Upgrade\r\n" "Access-Control-Allow-Origin: *\r\n" //allow cross-origin requests. this means you can use any domain to play on any public server. "Sec-WebSocket-Accept: %s\r\n" -// "Sec-WebSocket-Protocol: FTEWebSocket\r\n" - "\r\n", acceptkey); +// "%s" + "\r\n", acceptkey, protoname); //send the websocket handshake response. send(st->socketnum, resp, strlen(resp), 0); //and the connection is okay - if (!strcmp(arg[WCATTR_WSPROTO], "binary")) - st->clienttype = TCPC_WEBSOCKETB; //emscripten doesn't give us a choice, but its compact. - else - st->clienttype = TCPC_WEBSOCKETU; //nacl supports only utf-8 encoded data, at least at the time I implemented it. + + if (st->clienttype == TCPC_WEBSOCKETNQ) + { + //hide a connection request in there... + net_message.cursize = 0; + net_message.packing = SZ_RAWBYTES; + net_message.currentbit = 0; + net_from = st->remoteaddr; + MSG_WriteLong(&net_message, LongSwap(NETFLAG_CTL | (strlen(NET_GAMENAME_NQ)+7))); + MSG_WriteByte(&net_message, CCREQ_CONNECT); + MSG_WriteString(&net_message, NET_GAMENAME_NQ); + MSG_WriteByte(&net_message, NET_PROTOCOL_VERSION); + return true; + } } } else @@ -3053,6 +3094,7 @@ handshakeerror: return true; case TCPC_WEBSOCKETU: case TCPC_WEBSOCKETB: + case TCPC_WEBSOCKETNQ: while (st->inlen >= 2) { unsigned short ctrl = ((unsigned char*)st->inbuffer)[0]<<8 | ((unsigned char*)st->inbuffer)[1]; @@ -3175,12 +3217,22 @@ handshakeerror: case 2: /*binary frame*/ // Con_Printf ("websocket binary frame from %s\n", NET_AdrToString (adr, sizeof(adr), st->remoteaddr)); net_message.cursize = paylen; - if (net_message.cursize >= sizeof(net_message_buffer) ) + if (net_message.cursize+8 >= sizeof(net_message_buffer) ) { Con_TPrintf ("Warning: Oversize packet from %s\n", NET_AdrToString (adr, sizeof(adr), &net_from)); goto closesvstream; } - memcpy(net_message_buffer, st->inbuffer+payoffs, paylen); + if (st->clienttype == TCPC_WEBSOCKETNQ) + { + payoffs+=1; + paylen-=1; + memcpy(net_message_buffer+8, st->inbuffer+payoffs, paylen); + net_message.cursize=paylen+8; + ((int*)net_message_buffer)[0] = BigLong(NETFLAG_UNRELIABLE | net_message.cursize); + ((int*)net_message_buffer)[1] = LongSwap(++st->fakesequence); + } + else + memcpy(net_message_buffer, st->inbuffer+payoffs, paylen); break; case 8: /*connection close*/ Con_Printf ("websocket closure %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); @@ -3284,11 +3336,20 @@ qboolean FTENET_TCPConnect_SendPacket(ftenet_generic_connection_t *gcon, int len memcpy(st->outbuffer, data, length); st->outlen = length; break; + case TCPC_WEBSOCKETNQ: + if (length < 8 || ((char*)data)[0] & 0x80) + break; +// length = 2; +// data = "\1\1"; + length-=7; + data=(char*)data + 7; + *(char*)data = 1; //for compat with webquake, we add an extra byte at the start. 1 for reliable, 2 for unreliable. + //fallthrough case TCPC_WEBSOCKETU: case TCPC_WEBSOCKETB: { /*as a server, we don't need the mask stuff*/ - unsigned short ctrl = (st->clienttype==TCPC_WEBSOCKETB)?0x8200:0x8100; + unsigned short ctrl = (st->clienttype==TCPC_WEBSOCKETU)?0x8100:0x8200; unsigned int paylen = 0; unsigned int payoffs = 2; int i; diff --git a/engine/server/sv_main.c b/engine/server/sv_main.c index 51faea962..c0a54a06c 100644 --- a/engine/server/sv_main.c +++ b/engine/server/sv_main.c @@ -125,7 +125,7 @@ cvar_t allow_download_copyrighted = CVAR("allow_download_copyrighted", "0"); cvar_t sv_public = CVAR("sv_public", "0"); cvar_t sv_listen_qw = CVARAF("sv_listen_qw", "1", "sv_listen", 0); -cvar_t sv_listen_nq = CVARD("sv_listen_nq", "2", "Allow new (net)quake clients to connect to the server. 0 = don't let them in. 1 = allow them in (WARNING: this allows 'qsmurf' DOS attacks). 2 = accept (net)quake clients by emulating a challenge (as secure as QW/Q2 but does not fully conform to the NQ protocol)."); +cvar_t sv_listen_nq = CVARD("sv_listen_nq", "2", "Allow new (net)quake clients to connect to the server.\n0 = don't let them in.\n1 = allow them in (WARNING: this allows 'qsmurf' DOS attacks).\n2 = accept (net)quake clients by emulating a challenge (as secure as QW/Q2 but does not fully conform to the NQ protocol)."); cvar_t sv_listen_dp = CVAR("sv_listen_dp", "0"); /*kinda fucked right now*/ cvar_t sv_listen_q3 = CVAR("sv_listen_q3", "0"); cvar_t sv_reportheartbeats = CVAR("sv_reportheartbeats", "1"); @@ -2625,6 +2625,7 @@ client_t *SVC_DirectConnect(void) { SV_AcceptMessage (protocol); + newcl->state = cs_free; if (ISNQCLIENT(newcl)) { //FIXME: we should delay this until we actually have a name, because right now they'll be called unnamed or unconnected or something @@ -2643,6 +2644,7 @@ client_t *SVC_DirectConnect(void) SV_BroadcastTPrintf(PRINT_LOW, "client %s connected\n", newcl->name); // Con_DPrintf ("Client %s connected\n", newcl->name); } + newcl->state = cs_connected; } else { diff --git a/engine/server/sv_user.c b/engine/server/sv_user.c index 791b078a9..01b30d2e4 100644 --- a/engine/server/sv_user.c +++ b/engine/server/sv_user.c @@ -425,6 +425,7 @@ void SVNQ_New_f (void) int op; unsigned int protext1 = 0, protext2 = 0, protmain = 0, protfl = 0; char *protoname; + extern cvar_t sv_listen_nq; host_client->prespawn_stage = PRESPAWN_INVALID; host_client->prespawn_idx = 0; @@ -438,7 +439,7 @@ void SVNQ_New_f (void) return; } - if (!host_client->pextknown) + if (!host_client->pextknown && sv_listen_nq.ival != 1) //1 acts as a legacy mode, used for clients that can't cope with cmd before serverdata (either because they crash out or because they refuse to send reliables until after they got the first serverdata) { char *msg = va("cmd pext\n"); ClientReliableWrite_Begin (host_client, svc_stufftext, 2+strlen(msg));