mirror of
https://github.com/nzp-team/fteqw.git
synced 2025-02-22 19:41:27 +00:00
webquake-compatible websocket support. yuck.
git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@4563 fc73d0e0-1445-4013-8a0c-d673dee63da5
This commit is contained in:
parent
483403dc9a
commit
e2081b565f
4 changed files with 88 additions and 14 deletions
|
@ -553,6 +553,16 @@ int Netchan_Transmit (netchan_t *chan, int length, qbyte *data, int rate)
|
||||||
send.maxsize = MAX_NQMSGLEN + PACKET_HEADER;
|
send.maxsize = MAX_NQMSGLEN + PACKET_HEADER;
|
||||||
send.cursize = 0;
|
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*/
|
/*unreliables flood out, but reliables are tied to server sequences*/
|
||||||
if (chan->nqreliable_resendtime < realtime)
|
if (chan->nqreliable_resendtime < realtime)
|
||||||
chan->nqreliable_allowed = true;
|
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, 0);
|
||||||
MSG_WriteLong(&send, LongSwap(chan->reliable_sequence));
|
MSG_WriteLong(&send, LongSwap(chan->reliable_sequence));
|
||||||
if (i > MAX_NQDATAGRAM)
|
if (i > MAX_NQDATAGRAM && chan->remote_address.type != NA_TCP)
|
||||||
i = MAX_NQDATAGRAM;
|
i = MAX_NQDATAGRAM;
|
||||||
|
|
||||||
SZ_Write (&send, chan->reliable_buf+chan->reliable_start, i);
|
SZ_Write (&send, chan->reliable_buf+chan->reliable_start, i);
|
||||||
|
|
|
@ -1572,6 +1572,14 @@ qboolean FTENET_Loop_GetPacket(ftenet_generic_connection_t *con)
|
||||||
return NET_GetLoopPacket(con->thesocket, &net_from, &net_message);
|
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)
|
qboolean FTENET_Loop_SendPacket(ftenet_generic_connection_t *con, int length, void *data, netadr_t *to)
|
||||||
{
|
{
|
||||||
if (to->type == NA_LOOPBACK)
|
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->GetPacket = FTENET_Loop_GetPacket;
|
||||||
newcon->SendPacket = FTENET_Loop_SendPacket;
|
newcon->SendPacket = FTENET_Loop_SendPacket;
|
||||||
newcon->Close = FTENET_Loop_Close;
|
newcon->Close = FTENET_Loop_Close;
|
||||||
|
#ifdef HAVE_PACKET
|
||||||
|
newcon->SetReceiveFDSet = FTENET_Loop_SetReceiveFDSet;
|
||||||
|
#endif
|
||||||
|
|
||||||
newcon->islisten = isserver;
|
newcon->islisten = isserver;
|
||||||
newcon->addrtype[0] = NA_LOOPBACK;
|
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_QIZMO, //'qizmo\n' handshake, followed by packets prefixed with a 16bit packet length.
|
||||||
TCPC_WEBSOCKETU, //utf-8 encoded data.
|
TCPC_WEBSOCKETU, //utf-8 encoded data.
|
||||||
TCPC_WEBSOCKETB, //binary encoded data (subprotocol = 'binary')
|
TCPC_WEBSOCKETB, //binary encoded data (subprotocol = 'binary')
|
||||||
|
TCPC_WEBSOCKETNQ, //raw nq msg buffers with no encapsulation or handshake
|
||||||
} clienttype;
|
} clienttype;
|
||||||
char inbuffer[3000];
|
char inbuffer[3000];
|
||||||
char outbuffer[3000];
|
char outbuffer[3000];
|
||||||
|
@ -2612,6 +2624,8 @@ typedef struct ftenet_tcpconnect_stream_s {
|
||||||
float timeouttime;
|
float timeouttime;
|
||||||
netadr_t remoteaddr;
|
netadr_t remoteaddr;
|
||||||
struct ftenet_tcpconnect_stream_s *next;
|
struct ftenet_tcpconnect_stream_s *next;
|
||||||
|
|
||||||
|
int fakesequence; //TCPC_WEBSOCKETNQ
|
||||||
} ftenet_tcpconnect_stream_t;
|
} ftenet_tcpconnect_stream_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
@ -2896,29 +2910,56 @@ closesvstream:
|
||||||
char acceptkey[20*2];
|
char acceptkey[20*2];
|
||||||
unsigned char sha1digest[20];
|
unsigned char sha1digest[20];
|
||||||
char *blurgh;
|
char *blurgh;
|
||||||
|
char *protoname = "";
|
||||||
memmove(st->inbuffer, st->inbuffer+i, st->inlen - (i));
|
memmove(st->inbuffer, st->inbuffer+i, st->inlen - (i));
|
||||||
st->inlen -= i;
|
st->inlen -= i;
|
||||||
|
|
||||||
blurgh = va("%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11", arg[WCATTR_WSKEY]);
|
blurgh = va("%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11", arg[WCATTR_WSKEY]);
|
||||||
tobase64(acceptkey, sizeof(acceptkey), sha1digest, SHA1(sha1digest, sizeof(sha1digest), blurgh, strlen(blurgh)));
|
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"
|
"Upgrade: websocket\r\n"
|
||||||
"Connection: Upgrade\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.
|
"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-Accept: %s\r\n"
|
||||||
// "Sec-WebSocket-Protocol: FTEWebSocket\r\n"
|
// "%s"
|
||||||
"\r\n", acceptkey);
|
"\r\n", acceptkey, protoname);
|
||||||
//send the websocket handshake response.
|
//send the websocket handshake response.
|
||||||
send(st->socketnum, resp, strlen(resp), 0);
|
send(st->socketnum, resp, strlen(resp), 0);
|
||||||
|
|
||||||
//and the connection is okay
|
//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.
|
if (st->clienttype == TCPC_WEBSOCKETNQ)
|
||||||
else
|
{
|
||||||
st->clienttype = TCPC_WEBSOCKETU; //nacl supports only utf-8 encoded data, at least at the time I implemented it.
|
//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
|
else
|
||||||
|
@ -3053,6 +3094,7 @@ handshakeerror:
|
||||||
return true;
|
return true;
|
||||||
case TCPC_WEBSOCKETU:
|
case TCPC_WEBSOCKETU:
|
||||||
case TCPC_WEBSOCKETB:
|
case TCPC_WEBSOCKETB:
|
||||||
|
case TCPC_WEBSOCKETNQ:
|
||||||
while (st->inlen >= 2)
|
while (st->inlen >= 2)
|
||||||
{
|
{
|
||||||
unsigned short ctrl = ((unsigned char*)st->inbuffer)[0]<<8 | ((unsigned char*)st->inbuffer)[1];
|
unsigned short ctrl = ((unsigned char*)st->inbuffer)[0]<<8 | ((unsigned char*)st->inbuffer)[1];
|
||||||
|
@ -3175,11 +3217,21 @@ handshakeerror:
|
||||||
case 2: /*binary frame*/
|
case 2: /*binary frame*/
|
||||||
// Con_Printf ("websocket binary frame from %s\n", NET_AdrToString (adr, sizeof(adr), st->remoteaddr));
|
// Con_Printf ("websocket binary frame from %s\n", NET_AdrToString (adr, sizeof(adr), st->remoteaddr));
|
||||||
net_message.cursize = paylen;
|
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));
|
Con_TPrintf ("Warning: Oversize packet from %s\n", NET_AdrToString (adr, sizeof(adr), &net_from));
|
||||||
goto closesvstream;
|
goto closesvstream;
|
||||||
}
|
}
|
||||||
|
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);
|
memcpy(net_message_buffer, st->inbuffer+payoffs, paylen);
|
||||||
break;
|
break;
|
||||||
case 8: /*connection close*/
|
case 8: /*connection close*/
|
||||||
|
@ -3284,11 +3336,20 @@ qboolean FTENET_TCPConnect_SendPacket(ftenet_generic_connection_t *gcon, int len
|
||||||
memcpy(st->outbuffer, data, length);
|
memcpy(st->outbuffer, data, length);
|
||||||
st->outlen = length;
|
st->outlen = length;
|
||||||
break;
|
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_WEBSOCKETU:
|
||||||
case TCPC_WEBSOCKETB:
|
case TCPC_WEBSOCKETB:
|
||||||
{
|
{
|
||||||
/*as a server, we don't need the mask stuff*/
|
/*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 paylen = 0;
|
||||||
unsigned int payoffs = 2;
|
unsigned int payoffs = 2;
|
||||||
int i;
|
int i;
|
||||||
|
|
|
@ -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_public = CVAR("sv_public", "0");
|
||||||
cvar_t sv_listen_qw = CVARAF("sv_listen_qw", "1", "sv_listen", 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_dp = CVAR("sv_listen_dp", "0"); /*kinda fucked right now*/
|
||||||
cvar_t sv_listen_q3 = CVAR("sv_listen_q3", "0");
|
cvar_t sv_listen_q3 = CVAR("sv_listen_q3", "0");
|
||||||
cvar_t sv_reportheartbeats = CVAR("sv_reportheartbeats", "1");
|
cvar_t sv_reportheartbeats = CVAR("sv_reportheartbeats", "1");
|
||||||
|
@ -2625,6 +2625,7 @@ client_t *SVC_DirectConnect(void)
|
||||||
{
|
{
|
||||||
SV_AcceptMessage (protocol);
|
SV_AcceptMessage (protocol);
|
||||||
|
|
||||||
|
newcl->state = cs_free;
|
||||||
if (ISNQCLIENT(newcl))
|
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
|
//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);
|
SV_BroadcastTPrintf(PRINT_LOW, "client %s connected\n", newcl->name);
|
||||||
// Con_DPrintf ("Client %s connected\n", newcl->name);
|
// Con_DPrintf ("Client %s connected\n", newcl->name);
|
||||||
}
|
}
|
||||||
|
newcl->state = cs_connected;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -425,6 +425,7 @@ void SVNQ_New_f (void)
|
||||||
int op;
|
int op;
|
||||||
unsigned int protext1 = 0, protext2 = 0, protmain = 0, protfl = 0;
|
unsigned int protext1 = 0, protext2 = 0, protmain = 0, protfl = 0;
|
||||||
char *protoname;
|
char *protoname;
|
||||||
|
extern cvar_t sv_listen_nq;
|
||||||
|
|
||||||
host_client->prespawn_stage = PRESPAWN_INVALID;
|
host_client->prespawn_stage = PRESPAWN_INVALID;
|
||||||
host_client->prespawn_idx = 0;
|
host_client->prespawn_idx = 0;
|
||||||
|
@ -438,7 +439,7 @@ void SVNQ_New_f (void)
|
||||||
return;
|
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");
|
char *msg = va("cmd pext\n");
|
||||||
ClientReliableWrite_Begin (host_client, svc_stufftext, 2+strlen(msg));
|
ClientReliableWrite_Begin (host_client, svc_stufftext, 2+strlen(msg));
|
||||||
|
|
Loading…
Reference in a new issue