From 527233154c177c4c924d004c7dbb4637cbdfed02 Mon Sep 17 00:00:00 2001 From: Shpoike Date: Wed, 15 Mar 2023 17:57:31 +0000 Subject: [PATCH] Clients can now request the master to initiate an WebRTC/ICE connection with 'sv_public 1' servers. This is primarily for the browser port. Servers now report their srflx address via the status command (with fp, ready for secure copy+paste). --- CMakeLists.txt | 1 + engine/client/cl_main.c | 60 +-- engine/client/cl_parse.c | 3 +- engine/client/in_generic.c | 2 +- engine/client/m_multi.c | 4 +- engine/client/net_master.c | 245 +++++++---- engine/client/sys_linux.c | 2 +- engine/common/bothdefs.h | 4 +- engine/common/log.c | 1 - engine/common/net.h | 17 +- engine/common/net_ice.c | 539 +++++++++++++++++++----- engine/common/net_wins.c | 824 +++++++++++++++++++++++++++---------- engine/common/netinc.h | 5 +- engine/common/zone.c | 10 + engine/common/zone.h | 1 + engine/http/httpserver.c | 6 +- engine/server/pr_cmds.c | 33 +- engine/server/server.h | 5 +- engine/server/sv_ccmds.c | 6 - engine/server/sv_main.c | 39 +- engine/server/sv_master.c | 281 ++++++++----- engine/server/sv_mvd.c | 2 +- engine/server/sv_send.c | 3 +- engine/server/sv_user.c | 6 + engine/web/ftejslib.js | 25 ++ engine/web/sys_web.c | 44 +- 26 files changed, 1598 insertions(+), 570 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 602fcb863..9911d17ed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1026,6 +1026,7 @@ ELSE() engine/common/cvar.c engine/common/cmd.c engine/common/sha1.c #for websockets + engine/common/sha2.c #for fingerprints engine/http/httpclient.c #for the pipe stuff engine/common/log.c engine/common/fs.c diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c index 7821142db..ecc25f71f 100644 --- a/engine/client/cl_main.c +++ b/engine/client/cl_main.c @@ -187,9 +187,6 @@ cvar_t cl_gunanglex = CVAR("cl_gunanglex", "0"); cvar_t cl_gunangley = CVAR("cl_gunangley", "0"); cvar_t cl_gunanglez = CVAR("cl_gunanglez", "0"); -#ifdef HAVE_DTLS -extern cvar_t net_enable_dtls; -#endif cvar_t cl_proxyaddr = CVAR("cl_proxyaddr", ""); cvar_t cl_sendguid = CVARD("cl_sendguid", "", "Send a randomly generated 'globally unique' id to servers, which can be used by servers for score rankings and stuff. Different servers will see different guids. Delete the 'qkey' file in order to appear as a different user.\nIf set to 2, all servers will see the same guid. Be warned that this can show other people the guid that you're using."); cvar_t cl_downloads = CVARAFD("cl_downloads", "1", /*q3*/"cl_allowDownload", CVAR_NOTFROMSERVER, "Allows you to block all automatic downloads."); @@ -614,6 +611,18 @@ static void CL_ConnectAbort(const char *format, ...) connectinfo.numadr = 0; SCR_EndLoadingPlaque(); connectinfo.trying = false; + + if (format) + { + //try and force the menu to show again. this should force the disconnectreason to show. + if (!Key_Dest_Has(kdm_console)) + { +#ifdef MENU_DAT + if (!MP_Toggle(1)) +#endif + Menu_Prompt(NULL, NULL, reason, NULL, NULL, "Okay", true); + } + } } /* @@ -679,6 +688,7 @@ static void CL_SendConnectPacket (netadr_t *to) t1 = Sys_DoubleTime (); +#ifdef HAVE_DTLS if (connectinfo.peercred.hash && net_enable_dtls.ival>0) { char cert[8192]; @@ -693,6 +703,7 @@ static void CL_SendConnectPacket (netadr_t *to) return; } } +#endif if (!to) { @@ -844,7 +855,7 @@ static void CL_ResolvedServer(void *vctx, void *data, size_t a, size_t b) if (!ctx->found) { - CL_ConnectAbort("Bad server address \"%s\"\n", ctx->servername); + CL_ConnectAbort("Unable to resolve server address \"%s\"\n", ctx->servername); return; } @@ -1293,7 +1304,7 @@ void CL_CheckForResend (void) connectinfo.clogged = false; if (connectinfo.tries == 0 && connectinfo.nextadr < connectinfo.numadr) - if (!NET_EnsureRoute(cls.sockets, "conn", &connectinfo.peercred, to)) + if (!NET_EnsureRoute(cls.sockets, "conn", &connectinfo.peercred, to, true)) { CL_ConnectAbort ("Unable to establish connection to %s\n", cls.servername); return; @@ -1499,8 +1510,15 @@ static void CL_BeginServerConnect(char *host, int port, qboolean noproxy, enum c *e=0; if (!strncasecmp(arglist, "fp=", 3)) { - Base64_DecodeBlock(arglist+3, arglist+strlen(arglist), connectinfo.peercred.digest, sizeof(connectinfo.peercred.digest)); - connectinfo.peercred.hash = &hash_sha1; + size_t l = 8*Base64_DecodeBlock(arglist+3, arglist+strlen(arglist), connectinfo.peercred.digest, sizeof(connectinfo.peercred.digest)); + if (l <= 160) + connectinfo.peercred.hash = &hash_sha1; + else if (l <= 256) + connectinfo.peercred.hash = &hash_sha2_256; + else if (l <= 512) + connectinfo.peercred.hash = &hash_sha2_512; + else + connectinfo.peercred.hash = NULL; } else Con_Printf(CON_WARNING"uri arg not known: \"%s\"\n", arglist); @@ -3110,7 +3128,7 @@ void CL_Packet_f (void) if (!cls.sockets) NET_InitClient(false); - if (!NET_EnsureRoute(cls.sockets, "packet", &cred, &adr)) + if (!NET_EnsureRoute(cls.sockets, "packet", &cred, &adr, true)) return; NET_SendPacket (cls.sockets, out-send, send, &adr); @@ -3432,7 +3450,7 @@ void CL_ConnectionlessPacket (void) if (CL_IsPendingServerAddress(&net_from)) { struct dtlspeercred_s cred = {cls.servername}; //FIXME - if (!NET_EnsureRoute(cls.sockets, "redir", &cred, &adr)) + if (!NET_EnsureRoute(cls.sockets, "redir", &cred, &adr, true)) Con_Printf (CON_ERROR"Unable to redirect to %s\n", data); else { @@ -3896,7 +3914,7 @@ void CL_ConnectionlessPacket (void) memset(&cred, 0, sizeof(cred)); cred.peer = connectinfo.peercred; - if (NET_DTLS_Create(cls.sockets, &net_from, &cred)) + if (NET_DTLS_Create(cls.sockets, &net_from, &cred, true)) { connectinfo.numadr = 1; //fixate on this resolved address. connectinfo.adr[0] = net_from; @@ -3942,15 +3960,6 @@ client_connect: //fixme: make function Con_TPrintf ("ignoring connection\n"); return; } - if (net_from.type != NA_LOOPBACK) - { - Con_TPrintf (S_COLOR_GRAY"connection\n"); - -#ifdef HAVE_SERVER - if (sv.state && sv.state != ss_clustermode) - SV_UnspawnServer(); -#endif - } if (cls.state >= ca_connected) { @@ -3968,6 +3977,15 @@ client_connect: //fixme: make function return; } } + if (net_from.type != NA_LOOPBACK) + { +// Con_TPrintf (S_COLOR_GRAY"connection\n"); + +#ifdef HAVE_SERVER + if (sv.state && sv.state != ss_clustermode) + SV_UnspawnServer(); +#endif + } connectinfo.trying = false; cl.splitclients = 0; cls.protocol = connectinfo.protocol; @@ -4383,10 +4401,6 @@ void CL_ReadPackets (void) else NET_ReadPackets(cls.sockets); -#ifdef HAVE_DTLS - NET_DTLS_Timeouts(cls.sockets); -#endif - // // check timeout // diff --git a/engine/client/cl_parse.c b/engine/client/cl_parse.c index 199615283..df7d56c75 100644 --- a/engine/client/cl_parse.c +++ b/engine/client/cl_parse.c @@ -1499,6 +1499,7 @@ static int CL_LoadModels(int stage, qboolean dontactuallyload) if (atstage()) { SCR_SetLoadingFile("newmap"); + // if (!cl.worldmodel || cl.worldmodel->type == mod_dummy) // Host_EndGame("No worldmodel was loaded\n"); Surf_NewMap (cl.worldmodel); @@ -3740,7 +3741,7 @@ void CL_ParseEstablished(void) else security = "^["S_COLOR_RED"plain-text\\tip\\"CON_WARNING"Do not type passwords as they can potentially be seen by network sniffers^]"; - Con_TPrintf ("Connected to ^["S_COLOR_BLUE"%s\\type\\connect %s^] (%s).\n", cls.servername, cls.servername, security); + Con_TPrintf ("\rConnected to ^["S_COLOR_BLUE"%s\\type\\connect %s^] (%s).\n", cls.servername, cls.servername, security); } } diff --git a/engine/client/in_generic.c b/engine/client/in_generic.c index 8b50da45a..e37dd2e66 100644 --- a/engine/client/in_generic.c +++ b/engine/client/in_generic.c @@ -63,7 +63,7 @@ void QDECL joyaxiscallback(cvar_t *var, char *oldvalue) else if (!Q_strcasecmp(end, "right") || !Q_strcasecmp(end, "turnright")) var->ival = 4*sign; else if (!Q_strcasecmp(end, "left") || !Q_strcasecmp(end, "turnleft")) - var->ival = 4*sign*1; + var->ival = 4*sign*-1; else if (!Q_strcasecmp(end, "up") || !Q_strcasecmp(end, "moveup")) var->ival = 5*sign; else if (!Q_strcasecmp(end, "down") || !Q_strcasecmp(end, "movedown")) diff --git a/engine/client/m_multi.c b/engine/client/m_multi.c index f9093fab0..a613e0820 100644 --- a/engine/client/m_multi.c +++ b/engine/client/m_multi.c @@ -780,10 +780,9 @@ void M_Menu_GameOptions_f (void) info->publicgame = MC_AddCombo (menu, 64, 160, y, "Public", publicoptions, bound(0, sv_public.ival+1, 4));y+=8; #if !defined(FTE_TARGET_WEB) && defined(HAVE_DTLS) { - extern cvar_t net_enable_dtls; static const char *encoptions[] = { - "None", + "Disabled", "Accept", "Request", "Require", @@ -1143,7 +1142,6 @@ void M_Menu_Network_f (void) NULL }; #ifdef HAVE_DTLS - extern cvar_t net_enable_dtls; static const char *dtlsopts[] = { "Disabled", "Accept", diff --git a/engine/client/net_master.c b/engine/client/net_master.c index 51863994c..8409a1ba2 100644 --- a/engine/client/net_master.c +++ b/engine/client/net_master.c @@ -352,7 +352,7 @@ static void SV_Master_SingleHeartbeat(net_masterlist_t *master) //Note that Darkplaces clients are supposed to be able to use the qw protocol, so it should be okay to heartbeat as Darkplaces-Quake here even when not doing any nq protocols. //either way, custom protocols tend to require ftemaster/dpmaster so we want to heartbeat regardless. #if defined(NQPROT) && !defined(QUAKETC) - if (sv_listen_dp.value || sv_listen_nq.value || strcasecmp(com_protocolname.string, "FTE-Quake")) +// if (sv_listen_dp.value || sv_listen_nq.value || strcasecmp(com_protocolname.string, "FTE-Quake")) #endif { //darkplaces here refers to the master server protocol, rather than the game protocol @@ -375,7 +375,7 @@ static void SV_Master_SingleHeartbeat(net_masterlist_t *master) if (sv_reportheartbeats.ival != 2 || !master->announced) { COM_Parse(master->cv.string); - Con_TPrintf ("Sending heartbeat to %s (%s)\n", NET_AdrToString (adr, sizeof(adr), na), com_token); + Con_TPrintf (S_COLOR_GRAY"Sending heartbeat to %s (%s)\n", NET_AdrToString (adr, sizeof(adr), na), com_token); } master->announced = true; } @@ -414,7 +414,7 @@ static void SV_Master_SingleHeartbeat(net_masterlist_t *master) struct thr_res { qboolean success; - netadr_t na[8]; + netadr_t na[MAX_MASTER_ADDRESSES]; char str[1]; //trailing }; static void SV_Master_Worker_Resolved(void *ctx, void *data, size_t a, size_t b) @@ -499,7 +499,7 @@ static void SV_Master_Worker_Resolved(void *ctx, void *data, size_t a, size_t b) if (NET_AddrIsReliable(na)) { struct dtlspeercred_s cred = {master->cv.string}; - NET_EnsureRoute(svs.sockets, master->cv.name, &cred, na); + NET_EnsureRoute(svs.sockets, master->cv.name, &cred, na, true); } //q2+qw masters are given a ping to verify that they're still up @@ -527,6 +527,39 @@ static void SV_Master_Worker_Resolved(void *ctx, void *data, size_t a, size_t b) } Z_Free(work); } + +#if defined(SUPPORT_ICE) +struct stunheader_s +{ + unsigned short msgtype; + unsigned short msglen; + unsigned int magiccookie; + unsigned int transactid[3]; +}; +static void SV_Master_Worker_Resolved_Broker(void *ctx, void *data, size_t a, size_t b) +{ + struct thr_res *work = data; + if (svs.sockets && work->na[0].type != NA_INVALID) //something resolved... + { + struct stunheader_s msg = {htons(1), htons(sizeof(msg)-20), BigLong(0x2112a442), {42,42,42}}; + + //randomize the transaction id to avoid poisoning. + if (!Sys_RandomBytes((qbyte*)msg.transactid, sizeof(msg.transactid))) + { //FIXME: not really random enough to avoid hacks. oh well. + msg.transactid[0] = rand(); + msg.transactid[1] = rand(); + msg.transactid[2] = rand(); + } + + svs.sockets->srflx_tid[0] = msg.transactid[0]; + svs.sockets->srflx_tid[1] = msg.transactid[1]; + svs.sockets->srflx_tid[2] = msg.transactid[2]; + + NET_SendPacket(svs.sockets, sizeof(msg), &msg, &work->na[0]); + } + Z_Free(work); +} +#endif //worker thread static void SV_Master_Worker_Resolve(void *ctx, void *data, size_t a, size_t b) { @@ -540,13 +573,19 @@ static void SV_Master_Worker_Resolve(void *ctx, void *data, size_t a, size_t b) { str = COM_ParseOut(str, token, sizeof(token)); if (*token) - found += NET_StringToAdr2(token, 0, &work->na[found], MAX_MASTER_ADDRESSES-found, NULL); + found += NET_StringToAdr2(token, 0, &work->na[found], countof(work->na)-found, NULL); if (first && found) break; //if we found one by name, don't try any fallback ip addresses. first = false; } work->success = !!found; - COM_AddWork(WG_MAIN, SV_Master_Worker_Resolved, NULL, work, a, b); + +#if defined(SUPPORT_ICE) + if (a==~(size_t)0) + COM_AddWork(WG_MAIN, SV_Master_Worker_Resolved_Broker, NULL, work, a, b); + else +#endif + COM_AddWork(WG_MAIN, SV_Master_Worker_Resolved, NULL, work, a, b); } /* @@ -560,7 +599,7 @@ let it know we are alive, and log information void SV_Master_Heartbeat (void) { int i; - int interval = bound(90, sv_heartbeat_interval.ival, 600); + int interval = bound(85, sv_heartbeat_interval.ival, 600); if (sv_public.ival<=0 || SSV_IsSubServer()) return; @@ -605,6 +644,18 @@ void SV_Master_Heartbeat (void) else SV_Master_SingleHeartbeat(&net_masterlist[i]); } + +#if defined(SUPPORT_ICE) + if (*net_ice_broker.string) + { + const char *s = net_ice_broker.string; + struct thr_res *work = Z_Malloc(sizeof(*work) + strlen(s)); + if (!strncmp(s, "tls://", 6) || !strncmp(s, "tcp://", 6)) + s+=6; //ignore weird prefixes here + strcpy(work->str, s); + COM_AddWork(WG_MAIN, SV_Master_Worker_Resolve, NULL, work, ~(size_t)0, 0); + } +#endif } #ifdef HAVE_LEGACY @@ -2521,26 +2572,88 @@ void SListOptionChanged(serverinfo_t *newserver) } } +static qboolean MasterInfo_ReadProtocol(serverinfo_t *info, const char *infostring) +{ + char *token = Info_ValueForKey(infostring, "protocol"); + if (*token) + { + //read the protocol number + info->protocol = strtoul(token, &token, 0); + + //and try to figure out which filter it should be under. + info->special &= ~SS_PROTOCOLMASK; + if (*token) + { + while (*token) + { + if (*token == 'w') + info->special |= SS_QUAKEWORLD; + else if (*token == 'n' || *token == 'd') + info->special |= SS_NETQUAKE; + else if (*token == 'x') + info->special |= SS_QEPROT; + else + continue; + break; + } + } + else switch(info->protocol) + { + case PROTOCOL_VERSION_QW: info->special |= SS_QUAKEWORLD; break; +#ifdef NQPROT + case PROTOCOL_VERSION_NQ: info->special |= SS_NETQUAKE; break; + case PROTOCOL_VERSION_H2: info->special |= SS_NETQUAKE; break; //erk + case PROTOCOL_VERSION_NEHD: info->special |= SS_NETQUAKE; break; + case PROTOCOL_VERSION_FITZ: info->special |= SS_NETQUAKE; break; + case PROTOCOL_VERSION_RMQ: info->special |= SS_NETQUAKE; break; + case PROTOCOL_VERSION_DP5: info->special |= SS_NETQUAKE; break; //dp actually says 3... but hey, that's dp being WEIRD. + case PROTOCOL_VERSION_DP6: info->special |= SS_NETQUAKE; break; + case PROTOCOL_VERSION_DP7: info->special |= SS_NETQUAKE; break; + case NQ_NETCHAN_VERSION_QEX:info->special |= SS_QEPROT; break; + case NQ_NETCHAN_VERSION: +#endif + default: + if ((info->special&SS_PROTOCOLMASK) == SS_UNKNOWN) + { //guesses... + if (PROTOCOL_VERSION_Q2 >= info->protocol && info->protocol >= PROTOCOL_VERSION_Q2_MIN) + info->special |= SS_QUAKE2; //q2 has a range! + else if (info->protocol > 60) + info->special |= SS_QUAKE3; + else if (!strcmp(Info_ValueForKey(infostring, "gamename"), "DarkPlaces-Quake") || *Info_ValueForKey(infostring, "nqprotocol")) + info->special |= SS_NETQUAKE; + else + info->special |= SS_QUAKEWORLD; + } + break; + } + return true; + } + info->protocol = 0; + return false; +} + #ifdef WEBCLIENT static void MasterInfo_ProcessHTTPInfo(serverinfo_t *srv, const char *info) { char adrbuf[MAX_ADR_SIZE]; if (info && (!(srv->status & SRVSTATUS_ALIVE) || srv->ping == PING_UNKNOWN)) { - if (srv->adr.prot == NP_RTC_TLS || srv->adr.prot == NP_RTC_TCP) + if (srv->adr.prot != NP_DGRAM) { srv->sends = 0; //no point pinging it, it won't work. srv->ping = PING_UNKNOWN; srv->status |= SRVSTATUS_ALIVE; //or at least wouldn't have been reported this time around. } - else - srv->sends = 1; //no point pinging it, it won't work. + Q_strncpyz(srv->name, Info_ValueForKey(info, "hostname"), sizeof(srv->name)); Q_strncpyz(srv->gamedir, Info_ValueForKey(info, "modname"), sizeof(srv->gamedir)); Q_strncpyz(srv->map, Info_ValueForKey(info, "mapname"), sizeof(srv->map)); srv->players = atoi(Info_ValueForKey(info, "clients")); srv->maxplayers = atoi(Info_ValueForKey(info, "maxclients")); + if (!MasterInfo_ReadProtocol(srv, info)) + srv->special = (srv->special&~SS_PROTOCOLMASK)|SS_QUAKEWORLD; //assume its an older fteqw server. + srv->numbots = 0; srv->numhumans = srv->players - srv->numbots; srv->freeslots = srv->maxplayers - srv->players; @@ -2564,7 +2677,7 @@ static void MasterInfo_ProcessHTTP(struct dl_download *dl) char *el; serverinfo_t *info; char linebuffer[2048]; - char *brokerid; + const char *brokerid; char *infostring; netadr_t brokeradr; @@ -2611,12 +2724,19 @@ static void MasterInfo_ProcessHTTP(struct dl_download *dl) if (*s == '#') //hash is a comment, apparently. continue; - for (infostring = s; *infostring && *infostring != ' '; ) + for (infostring = s; *infostring && *infostring != ' ' && *infostring != '\t'; ) infostring++; - if (*infostring == ' ') - *infostring++ = 0; + if (*infostring == ' ' || *infostring == '\t') + { + *infostring++ = 0; //null terminate the address + while(*infostring == ' ' || *infostring == '\t') + infostring++; //skip over any whitespace... + if (*infostring != '\\') + infostring = NULL; //err... no. not an info string. probably a comment. + } else infostring = NULL; + if (!strncmp(s, "ice:///", 7) || !strncmp(s, "ices:///", 8) || !strncmp(s, "rtc:///", 7) || !strncmp(s, "rtcs:///", 8)) { brokerid = s+((s[4]==':')?7:6); @@ -2624,10 +2744,14 @@ static void MasterInfo_ProcessHTTP(struct dl_download *dl) if (!*brokerid) continue; //invalid... } + else if (*s == '/') + { + brokerid = s; + adr = brokeradr; + } else { - brokerid = ""; - if (!NET_StringToAdr(s, 80, &adr)) + if (!NET_StringToAdr2(s, 80, &adr, 1, &brokerid)) continue; } @@ -2648,8 +2772,8 @@ static void MasterInfo_ProcessHTTP(struct dl_download *dl) info->special = 0; if (protocoltype == MP_QUAKEWORLD) info->special |= SS_QUAKEWORLD; - else if (protocoltype == MP_DPMASTER) - info->special |= SS_GETINFO; + else if (protocoltype == MP_DPMASTER) //actually ftemaster... so assume fteqw servers not ftenq ones unless otherwise indicated. + info->special |= SS_QUAKEWORLD|SS_GETINFO; #if defined(Q2CLIENT) || defined(Q2SERVER) else if (protocoltype == MP_QUAKE2) info->special |= SS_QUAKE2; @@ -3249,65 +3373,23 @@ static int CL_ReadServerInfo(char *msg, enum masterprotocol_e prototype, qboolea else if (!strncmp(DISTRIBUTION, Info_ValueForKey(msg, "*version"), strlen(DISTRIBUTION))) info->special |= SS_FTESERVER; - info->protocol = strtoul(Info_ValueForKey(msg, "protocol"), &token, 0); - if (info->protocol) - { - switch(info->protocol) - { - case PROTOCOL_VERSION_QW: info->special |= SS_QUAKEWORLD; break; -#ifdef NQPROT - case PROTOCOL_VERSION_NQ: info->special |= SS_NETQUAKE; break; - case PROTOCOL_VERSION_H2: info->special |= SS_NETQUAKE; break; //erk - case PROTOCOL_VERSION_NEHD: info->special |= SS_NETQUAKE; break; - case PROTOCOL_VERSION_FITZ: info->special |= SS_NETQUAKE; break; - case PROTOCOL_VERSION_RMQ: info->special |= SS_NETQUAKE; break; - case PROTOCOL_VERSION_DP5: info->special |= SS_NETQUAKE; break; //dp actually says 3... but hey, that's dp being WEIRD. - case PROTOCOL_VERSION_DP6: info->special |= SS_NETQUAKE; break; - case PROTOCOL_VERSION_DP7: info->special |= SS_NETQUAKE; break; - case NQ_NETCHAN_VERSION_QEX:info->special |= SS_QEPROT; break; - case NQ_NETCHAN_VERSION: -#endif - default: - while (*token) - { - if (*token == 'w') - info->special |= SS_QUAKEWORLD; - else if (*token == 'n' || *token == 'd') - info->special |= SS_NETQUAKE; - else if (*token == 'x') - info->special |= SS_QEPROT; - else - continue; - break; - } - if ((info->special&SS_PROTOCOLMASK) == SS_UNKNOWN) - { //guesses... - if (PROTOCOL_VERSION_Q2 >= info->protocol && info->protocol >= PROTOCOL_VERSION_Q2_MIN) - info->special |= SS_QUAKE2; //q2 has a range! - else if (info->protocol > 60) - info->special |= SS_QUAKE3; - else if (!strcmp(Info_ValueForKey(msg, "gamename"), "DarkPlaces-Quake") || *Info_ValueForKey(msg, "nqprotocol")) - info->special |= SS_NETQUAKE; - else - info->special |= SS_QUAKEWORLD; - } - break; - } - } + if (!MasterInfo_ReadProtocol(info, msg)) + { //try and guess. #ifdef Q2CLIENT - else if (prototype == MP_QUAKE2) - info->special |= SS_QUAKE2; + if (prototype == MP_QUAKE2) + info->special |= SS_QUAKE2; #endif #ifdef Q3CLIENT - else if (prototype == MP_QUAKE3 || prototype == MP_DPMASTER/*if no protocol, assume q3 behaviours*/) - info->special |= SS_QUAKE3; + else if (prototype == MP_QUAKE3 || prototype == MP_DPMASTER/*if no protocol, assume q3 behaviours*/) + info->special |= SS_QUAKE3; #endif #ifdef NQPROT - else if (prototype == MP_NETQUAKE) - info->special |= SS_NETQUAKE; + else if (prototype == MP_NETQUAKE) + info->special |= SS_NETQUAKE; #endif - else - info->special |= SS_QUAKEWORLD; + else + info->special |= SS_QUAKEWORLD; + } if (favorite) //was specifically named, not retrieved from a master. info->special |= SS_FAVORITE; @@ -3786,16 +3868,19 @@ static void NetQ3_GlobalServers_Request(size_t masternum, int protocol, const ch const char *url; struct dl_download *dl; COM_Parse(com_protocolname.string); - if (!strncmp(net_ice_broker.string, "tls://", 6)) - url = va("https://%s/raw/%s", net_ice_broker.string+6, com_token); - else if (!strncmp(net_ice_broker.string, "tcp://", 6)) - url = va("http://%s/raw/%s", net_ice_broker.string+6, com_token); - else - url = va("http://%s/raw/%s", net_ice_broker.string, com_token); + if (*net_ice_broker.string) + { + if (!strncmp(net_ice_broker.string, "tls://", 6)) + url = va("https://%s/raw/%s", net_ice_broker.string+6, com_token); + else if (!strncmp(net_ice_broker.string, "tcp://", 6)) + url = va("http://%s/raw/%s", net_ice_broker.string+6, com_token); + else + url = va("http://%s/raw/%s", net_ice_broker.string, com_token); - dl = HTTP_CL_Get(url, NULL, MasterInfo_ProcessHTTP); - if (dl) - dl->isquery = true; + dl = HTTP_CL_Get(url, NULL, MasterInfo_ProcessHTTP); + if (dl) + dl->isquery = true; + } } #endif #if POLLTOTALSOCKETS>0 diff --git a/engine/client/sys_linux.c b/engine/client/sys_linux.c index 19c4146b5..1f4d6ca52 100644 --- a/engine/client/sys_linux.c +++ b/engine/client/sys_linux.c @@ -213,7 +213,7 @@ void Sys_Printf (char *fmt, ...) if (w >= 0xe000 && w < 0xe100) { /*not all quake chars are ascii compatible, so map those control chars to safe ones so we don't mess up anyone's xterm*/ - if ((w & 0x7f) > 0x20) + if ((w & 0x7f) >= 0x20) putc(w&0x7f, out); else if (w & 0x80) { diff --git a/engine/common/bothdefs.h b/engine/common/bothdefs.h index 1bf83805e..a26128c35 100644 --- a/engine/common/bothdefs.h +++ b/engine/common/bothdefs.h @@ -429,9 +429,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. //FIXME: HAVE_WINSSPI does not work as a server. //FIXME: advertising dtls without a valid certificate will probably bug out if a client tries to auto-upgrade. //FIXME: we don't cache server certs - #ifndef MASTERONLY - #define HAVE_DTLS - #endif + #define HAVE_DTLS #endif #if defined(USE_SQLITE) || defined(USE_MYSQL) diff --git a/engine/common/log.c b/engine/common/log.c index a11a6dcb8..c0a0da5d2 100644 --- a/engine/common/log.c +++ b/engine/common/log.c @@ -796,7 +796,6 @@ static void CertLog_Add_Prompted(void *vctx, promptbutton_t button) } qboolean CertLog_ConnectOkay(const char *hostname, void *cert, size_t certsize, unsigned int certlogproblems) { //this is specifically for dtls certs. - extern cvar_t net_enable_dtls; struct certlog_s *l; qboolean trusted = (net_enable_dtls.ival >= 2); char digest[DIGEST_MAXSIZE]; diff --git a/engine/common/net.h b/engine/common/net.h index 447d96ca0..79a815134 100644 --- a/engine/common/net.h +++ b/engine/common/net.h @@ -152,17 +152,17 @@ int NET_LocalAddressForRemote(struct ftenet_connections_s *collection, netadr_ void NET_PrintAddresses(struct ftenet_connections_s *collection); qboolean NET_AddressSmellsFunny(netadr_t *a); struct dtlspeercred_s; -qboolean NET_EnsureRoute(struct ftenet_connections_s *collection, char *routename, const struct dtlspeercred_s *peerinfo, netadr_t *adr); +qboolean NET_EnsureRoute(struct ftenet_connections_s *collection, char *routename, const struct dtlspeercred_s *peerinfo, netadr_t *adr, qboolean outgoing); void NET_TerminateRoute(struct ftenet_connections_s *collection, netadr_t *adr); void NET_PrintConnectionsStatus(struct ftenet_connections_s *collection); enum addressscope_e { - ASCOPE_PROCESS=0, - ASCOPE_HOST=1, - ASCOPE_LINK=2, - ASCOPE_LAN=3, - ASCOPE_NET=4 + ASCOPE_PROCESS=0, //unusable + ASCOPE_HOST=1, //unroutable + ASCOPE_LINK=2, //unpredictable + ASCOPE_LAN=3, //private + ASCOPE_NET=4 //aka hopefully globally routable }; enum addressscope_e NET_ClassifyAddress(netadr_t *adr, const char **outdesc); @@ -175,6 +175,7 @@ char *NET_AdrToString (char *s, int len, netadr_t *a); char *NET_SockadrToString (char *s, int len, struct sockaddr_qstorage *a, size_t sizeofa); char *NET_BaseAdrToString (char *s, int len, netadr_t *a); size_t NET_StringToSockaddr2 (const char *s, int defaultport, netadrtype_t afhint, struct sockaddr_qstorage *sadr, int *addrfamily, int *addrsize, size_t addrcount); +qboolean NET_StringToAdr_NoDNS(const char *address, int port, netadr_t *out); #define NET_StringToSockaddr(s,p,a,f,z) (NET_StringToSockaddr2(s,p,NA_INVALID,a,f,z,1)>0) size_t NET_StringToAdr2 (const char *s, int defaultport, netadr_t *a, size_t addrcount, const char **pathstart); #define NET_StringToAdr(s,p,a) NET_StringToAdr2(s,p,a,1,NULL) @@ -203,11 +204,11 @@ int NET_GetConnectionCertificate(struct ftenet_connections_s *col, netadr_t *a, #ifdef HAVE_DTLS struct dtlscred_s; struct dtlsfuncs_s; -qboolean NET_DTLS_Create(struct ftenet_connections_s *col, netadr_t *to, const struct dtlscred_s *cred); +qboolean NET_DTLS_Create(struct ftenet_connections_s *col, netadr_t *to, const struct dtlscred_s *cred, qboolean outgoing); qboolean NET_DTLS_Decode(struct ftenet_connections_s *col); qboolean NET_DTLS_Disconnect(struct ftenet_connections_s *col, netadr_t *to); -void NET_DTLS_Timeouts(struct ftenet_connections_s *col); extern cvar_t dtls_psk_hint, dtls_psk_user, dtls_psk_key; +extern cvar_t net_enable_dtls; #endif #ifdef SUPPORT_ICE neterr_t ICE_SendPacket(size_t length, const void *data, netadr_t *to); diff --git a/engine/common/net_ice.c b/engine/common/net_ice.c index 906cd3137..c7f3867b5 100644 --- a/engine/common/net_ice.c +++ b/engine/common/net_ice.c @@ -1,3 +1,43 @@ +/* +Interactive Connectivity Establishment (rfc 5245) +find out your peer's potential ports. +spam your peer with stun packets. +see what sticks. +the 'controller' assigns some final candidate pair to ensure that both peers send+receive from a single connection. +if no candidates are available, try using stun to find public nat addresses. + +in fte, a 'pair' is actually in terms of each local socket and remote address. hopefully that won't cause too much weirdness. + this does limit which interfaces we can send packets from, which may cause issues with local TURN relays(does not result in extra prflx candidates) and VPNs(may need to be set up as the default route), and prevents us from being able to report reladdr in candidate offers (again mostly only of use with TURN) + lan connections should resolve to a host interface anyway + +stun test packets must contain all sorts of info. username+integrity+fingerprint for validation. priority+usecandidate+icecontrol(ing) to decree the priority of any new remote candidates, whether its finished, and just who decides whether its finished. +peers don't like it when those are missing. + +host candidates - addresses that are directly known (but are probably unroutable private things) +server reflexive candidates - addresses that we found from some public stun server (useful for NATs that use a single public port for each unique private port) +peer reflexive candidates - addresses that our peer finds out about as we spam them +relayed candidates - some sort of socks5 or something proxy. + + +Note: Even after the ICE connection becomes active, you should continue to collect local candidates and transmit them to the peer out of band. +this allows the connection to pick a new route if some router dies (like a relay kicking us). +FIXME: the client currently disconnects from the broker. the server tracks players via ip rather than ICE. + +tcp rtp framing should generally be done with a 16-bit network-endian length prefix followed by the data. + +NOTE: we do NOT distinguish between media-level and session-level attributes, as such we can only handle ONE media stream per session. we also don't support rtcp. +*/ +/* +webrtc +basically just sctp-over-dtls-over-ice or srtp(negotiated via dtls)-over-ice. +the sctp part is pure bloat+pain for us, but as its required for browser compat we have to support it anyway - but we only use it where we must. +we don't do any srtp stuff at all. +*/ +/* +broker +ftemaster provides a broker service +*/ + #include "quakedef.h" #include "netinc.h" @@ -77,36 +117,6 @@ static cvar_t net_ice_debug = CVARFD("net_ice_debug", "0", CVAR_NOTFROMSERVER, " #define ASCOPE_TURN_REQUIRESCOPE ASCOPE_LAN //don't report loopback/link-local addresses to turn relays. #endif -/* -Interactive Connectivity Establishment (rfc 5245) -find out your peer's potential ports. -spam your peer with stun packets. -see what sticks. -the 'controller' assigns some final candidate pair to ensure that both peers send+receive from a single connection. -if no candidates are available, try using stun to find public nat addresses. - -in fte, a 'pair' is actually in terms of each local socket and remote address. hopefully that won't cause too much weirdness. - this does limit which interfaces we can send packets from, which may cause issues with local TURN relays(does not result in extra prflx candidates) and VPNs(may need to be set up as the default route), and prevents us from being able to report reladdr in candidate offers (again mostly only of use with TURN) - lan connections should resolve to a host interface anyway - -stun test packets must contain all sorts of info. username+integrity+fingerprint for validation. priority+usecandidate+icecontrol(ing) to decree the priority of any new remote candidates, whether its finished, and just who decides whether its finished. -peers don't like it when those are missing. - -host candidates - addresses that are directly known (but are probably unroutable private things) -server reflexive candidates - addresses that we found from some public stun server (useful for NATs that use a single public port for each unique private port) -peer reflexive candidates - addresses that our peer finds out about as we spam them -relayed candidates - some sort of socks5 or something proxy. - - -Note: Even after the ICE connection becomes active, you should continue to collect local candidates and transmit them to the peer out of band. -this allows the connection to pick a new route if some router dies (like a relay kicking us). -FIXME: the client currently disconnects from the broker. the server tracks players via ip rather than ICE. - -tcp rtp framing should generally be done with a 16-bit network-endian length prefix followed by the data. - -NOTE: we do NOT distinguish between media-level and session-level attributes, as such we can only handle ONE media stream per session. we also don't support rtcp. -*/ - struct icecandidate_s { struct icecandinfo_s info; @@ -214,10 +224,22 @@ struct icestate_s int id; char *name; } codecslot[34]; //96-127. don't really need to care about other ones. + + struct + { //this block is for our inbound udp broker reliability, ensuring we get candidate info to where its needed... + char *text; + unsigned int inseq; + unsigned int outseq; + } u; }; typedef struct sctp_s { + char *friendlyname; //for printing/debugging. + struct icestate_s *icestate; //for forwarding over ice connections... + const struct dtlsfuncs_s *dtlsfuncs; //for forwarding over dtls connects (ice-lite) + void *dtlsstate; + quint16_t myport, peerport; qboolean peerhasfwdtsn; double nextreinit; @@ -271,12 +293,11 @@ static const struct {"sha-384", &hash_sha2_384}, {"sha-512", &hash_sha2_512}, }; -extern cvar_t net_enable_dtls; -static neterr_t SCTP_Transmit(sctp_t *sctp, struct icestate_s *peer, const void *data, size_t length); #endif static neterr_t ICE_Transmit(void *cbctx, const qbyte *data, size_t datasize); static neterr_t TURN_Encapsulate(struct icestate_s *ice, netadr_t *to, const qbyte *data, size_t datasize); static void TURN_AuthorisePeer(struct icestate_s *con, struct iceserver_s *srv, int peer); +static neterr_t SCTP_Transmit(sctp_t *sctp, const void *data, size_t length); static struct icestate_s *icelist; @@ -684,7 +705,8 @@ static struct icestate_s *QDECL ICE_Create(void *module, const char *conname, co con = Z_Malloc(sizeof(*con)); con->conname = Z_StrDup(conname); - con->friendlyname = peername?Z_StrDup(peername):Z_StrDupf("%i", icenum++); + icenum++; + con->friendlyname = peername?Z_StrDup(peername):Z_StrDupf("%i", icenum); con->proto = proto; con->rpwd = Z_StrDup(""); con->rufrag = Z_StrDup(""); @@ -739,6 +761,7 @@ static struct icestate_s *QDECL ICE_Create(void *module, const char *conname, co con->qadr.type = NA_ICE; con->qadr.prot = NP_DGRAM; Q_strncpyz(con->qadr.address.icename, con->friendlyname, sizeof(con->qadr.address.icename)); + con->qadr.port = icenum; con->mode = mode; @@ -2155,6 +2178,8 @@ static qboolean QDECL ICE_Set(struct icestate_s *con, const char *prop, const ch if (!con->sctp && (!con->sctpoptional || !con->peersctpoptional) && con->mysctpport && con->peersctpport) { con->sctp = Z_Malloc(sizeof(*con->sctp)); + con->sctp->icestate = con; + con->sctp->friendlyname = con->friendlyname; con->sctp->myport = htons(con->mysctpport); con->sctp->peerport = htons(con->peersctpport); con->sctp->o.tsn = rand() ^ (rand()<<16); @@ -2580,6 +2605,16 @@ static qboolean QDECL ICE_Get(struct icestate_s *con, const char *prop, char *va } // Q_strncatz(value, va("c=IN %s %s\n", sender.type==NA_IPV6?"IP6":"IP4", NET_BaseAdrToString(tmpstr, sizeof(tmpstr), &sender)), valuelen); Q_strncatz(value, "c=IN IP4 0.0.0.0\n", valuelen); + + for (can = con->lc; can; can = can->next) + { + char canline[256]; + can->dirty = false; //doesn't matter now. + ICE_CandidateToSDP(can, canline, sizeof(canline)); + Q_strncatz(value, canline, valuelen); + Q_strncatz(value, "\n", valuelen); + } + Q_strncatz(value, va("a=ice-pwd:%s\n", con->lpwd), valuelen); Q_strncatz(value, va("a=ice-ufrag:%s\n", con->lufrag), valuelen); @@ -3065,7 +3100,7 @@ void ICE_Tick(void) { #ifdef HAVE_DTLS if (con->sctp) - SCTP_Transmit(con->sctp, con, NULL,0); //try to keep it ticking... + SCTP_Transmit(con->sctp, NULL,0); //try to keep it ticking... if (con->dtlsstate) con->dtlsfuncs->Timeouts(con->dtlsstate); #endif @@ -3125,7 +3160,8 @@ icefuncs_t iceapi = ICE_AddRCandidateInfo, ICE_Close, ICE_CloseModule, - ICE_GetLCandidateSDP + ICE_GetLCandidateSDP, + ICE_Find }; #endif @@ -3208,9 +3244,12 @@ struct sctp_chunk_fwdtsn_s } streams[];*/ }; -static neterr_t SCTP_PeerSendPacket(struct icestate_s *peer, int length, const void *data) +static neterr_t SCTP_PeerSendPacket(sctp_t *sctp, int length, const void *data) { //sends to the dtls layer (which will send to the generic ice dispatcher that'll send to the dgram stuff... layers on layers. - if (peer) + struct icestate_s *peer = sctp->icestate; + if (sctp->dtlsstate) + return sctp->dtlsfuncs->Transmit(sctp->dtlsstate, data, length); + else if (peer) { if (peer->dtlsstate) return peer->dtlsfuncs->Transmit(peer->dtlsstate, data, length); @@ -3244,7 +3283,7 @@ static quint32_t SCTP_Checksum(const struct sctp_header_s *h, size_t size) return ~crc; } -static neterr_t SCTP_Transmit(sctp_t *sctp, struct icestate_s *peer, const void *data, size_t length) +neterr_t SCTP_Transmit(sctp_t *sctp, const void *data, size_t length) { qbyte pkt[65536]; size_t pktlen = 0; @@ -3298,7 +3337,7 @@ static neterr_t SCTP_Transmit(sctp_t *sctp, struct icestate_s *peer, const void pktlen += sizeof(*init) + sizeof(*ftsn); h->crc = SCTP_Checksum(h, pktlen); - return SCTP_PeerSendPacket(peer, pktlen, h); + return SCTP_PeerSendPacket(sctp, pktlen, h); } else { @@ -3313,7 +3352,7 @@ static neterr_t SCTP_Transmit(sctp_t *sctp, struct icestate_s *peer, const void pktlen += sizeof(*cookie) + sctp->cookiesize; h->crc = SCTP_Checksum(h, pktlen); - return SCTP_PeerSendPacket(peer, pktlen, h); + return SCTP_PeerSendPacket(sctp, pktlen, h); } } @@ -3385,7 +3424,7 @@ static neterr_t SCTP_Transmit(sctp_t *sctp, struct icestate_s *peer, const void if (pktlen + sizeof(*d) + length >= 500 && length && pktlen != sizeof(*h)) { //probably going to result in fragmentation issues. send separate packets. h->crc = SCTP_Checksum(h, pktlen); - SCTP_PeerSendPacket(peer, pktlen, h); + SCTP_PeerSendPacket(sctp, pktlen, h); //reset to the header pktlen = sizeof(*h); @@ -3412,10 +3451,10 @@ static neterr_t SCTP_Transmit(sctp_t *sctp, struct icestate_s *peer, const void return NETERR_SENT; //nothing to send... h->crc = SCTP_Checksum(h, pktlen); - return SCTP_PeerSendPacket(peer, pktlen, h); + return SCTP_PeerSendPacket(sctp, pktlen, h); } -static void SCTP_DecodeDCEP(sctp_t *sctp, struct icestate_s *peer, qbyte *resp) +static void SCTP_DecodeDCEP(sctp_t *sctp, qbyte *resp) { //send an ack... size_t pktlen = 0; struct sctp_header_s *h = (void*)resp; @@ -3440,7 +3479,7 @@ static void SCTP_DecodeDCEP(sctp_t *sctp, struct icestate_s *peer, qbyte *resp) sctp->qstreamid = sctp->i.r.sid; if (net_ice_debug.ival >= 1) - Con_Printf(S_COLOR_GRAY"[%s]: New SCTP Channel: \"%s\" (%s)\n", peer->friendlyname, label, prot); + Con_Printf(S_COLOR_GRAY"[%s]: New SCTP Channel: \"%s\" (%s)\n", sctp->friendlyname, label, prot); h->dstport = sctp->peerport; h->srcport = sctp->myport; @@ -3460,7 +3499,7 @@ static void SCTP_DecodeDCEP(sctp_t *sctp, struct icestate_s *peer, qbyte *resp) pktlen += sizeof(*d) + length; h->crc = SCTP_Checksum(h, pktlen); - SCTP_PeerSendPacket(peer, pktlen, h); + SCTP_PeerSendPacket(sctp, pktlen, h); } } @@ -3469,7 +3508,7 @@ struct sctp_errorcause_s quint16_t cause; quint16_t length; }; -static void SCTP_ErrorChunk(struct icestate_s *peer, const char *errortype, struct sctp_errorcause_s *s, size_t totallen) +static void SCTP_ErrorChunk(sctp_t *sctp, const char *errortype, struct sctp_errorcause_s *s, size_t totallen) { quint16_t cc, cl; while(totallen > 0) @@ -3483,20 +3522,20 @@ static void SCTP_ErrorChunk(struct icestate_s *peer, const char *errortype, stru if (net_ice_debug.ival >= 1) switch(cc) { - case 1: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Invalid Stream Identifier\n", peer->friendlyname, errortype); break; - case 2: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Missing Mandatory Parameter\n", peer->friendlyname, errortype); break; - case 3: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Stale Cookie Error\n", peer->friendlyname, errortype); break; - case 4: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Out of Resource\n", peer->friendlyname, errortype); break; - case 5: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Unresolvable Address\n", peer->friendlyname, errortype); break; - case 6: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Unrecognized Chunk Type\n", peer->friendlyname, errortype); break; - case 7: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Invalid Mandatory Parameter\n", peer->friendlyname, errortype); break; - case 8: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Unrecognized Parameters\n", peer->friendlyname, errortype); break; - case 9: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: No User Data\n", peer->friendlyname, errortype); break; - case 10: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Cookie Received While Shutting Down\n", peer->friendlyname, errortype); break; - case 11: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Restart of an Association with New Addresses\n", peer->friendlyname, errortype); break; - case 12: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: User Initiated Abort\n", peer->friendlyname, errortype); break; - case 13: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Protocol Violation [%s]\n", peer->friendlyname, errortype, (char*)(s+1)); break; - default: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Unknown Reason\n", peer->friendlyname, errortype); break; + case 1: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Invalid Stream Identifier\n", sctp->friendlyname, errortype); break; + case 2: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Missing Mandatory Parameter\n", sctp->friendlyname, errortype); break; + case 3: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Stale Cookie Error\n", sctp->friendlyname, errortype); break; + case 4: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Out of Resource\n", sctp->friendlyname, errortype); break; + case 5: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Unresolvable Address\n", sctp->friendlyname, errortype); break; + case 6: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Unrecognized Chunk Type\n", sctp->friendlyname, errortype); break; + case 7: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Invalid Mandatory Parameter\n", sctp->friendlyname, errortype); break; + case 8: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Unrecognized Parameters\n", sctp->friendlyname, errortype); break; + case 9: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: No User Data\n", sctp->friendlyname, errortype); break; + case 10: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Cookie Received While Shutting Down\n", sctp->friendlyname, errortype); break; + case 11: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Restart of an Association with New Addresses\n", sctp->friendlyname, errortype); break; + case 12: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: User Initiated Abort\n", sctp->friendlyname, errortype); break; + case 13: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Protocol Violation [%s]\n", sctp->friendlyname, errortype, (char*)(s+1)); break; + default: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Unknown Reason\n", sctp->friendlyname, errortype); break; } totallen -= cl; @@ -3505,7 +3544,7 @@ static void SCTP_ErrorChunk(struct icestate_s *peer, const char *errortype, stru } } -static void SCTP_Decode(sctp_t *sctp, struct icestate_s *peer, ftenet_connections_t *col) +void SCTP_Decode(sctp_t *sctp, ftenet_connections_t *col) { qbyte resp[4096]; @@ -3527,7 +3566,7 @@ static void SCTP_Decode(sctp_t *sctp, struct icestate_s *peer, ftenet_connection if (net_message.cursize&3) { if (net_ice_debug.ival >= 2) - Con_Printf(S_COLOR_GRAY"[%s]: SCTP: packet not padded\n", peer->friendlyname); + Con_Printf(S_COLOR_GRAY"[%s]: SCTP: packet not padded\n", sctp->friendlyname); return; //mimic chrome, despite it being pointless. } @@ -3558,17 +3597,17 @@ static void SCTP_Decode(sctp_t *sctp, struct icestate_s *peer, ftenet_connection if (adv >= SCTP_RCVSIZE) { if (net_ice_debug.ival >= 1) - Con_Printf(S_COLOR_GRAY"[%s]: SCTP: Future Packet\n", peer->friendlyname);/*too far in the future. we can't track such things*/ + Con_Printf(S_COLOR_GRAY"[%s]: SCTP: Future Packet\n", sctp->friendlyname);/*too far in the future. we can't track such things*/ } else if (adv <= 0) { if (net_ice_debug.ival >= 2) - Con_Printf(S_COLOR_GRAY"[%s]: SCTP: PreCumulative\n", peer->friendlyname);/*already acked this*/ + Con_Printf(S_COLOR_GRAY"[%s]: SCTP: PreCumulative\n", sctp->friendlyname);/*already acked this*/ } else if (sctp->i.received[(tsn>>3)%sizeof(sctp->i.received)] & 1<<(tsn&7)) { if (net_ice_debug.ival >= 2) - Con_DPrintf(S_COLOR_GRAY"[%s]: SCTP: Dupe\n", peer->friendlyname);/*already processed it. FIXME: Make a list for the next SACK*/ + Con_DPrintf(S_COLOR_GRAY"[%s]: SCTP: Dupe\n", sctp->friendlyname);/*already processed it. FIXME: Make a list for the next SACK*/ } else { @@ -3600,7 +3639,7 @@ static void SCTP_Decode(sctp_t *sctp, struct icestate_s *peer, ftenet_connection if (sctp->i.r.size + dlen > sizeof(sctp->i.r.buf)) { if (net_ice_debug.ival >= 2) - Con_Printf(S_COLOR_GRAY"[%s]: SCTP: Oversized\n", peer->friendlyname); + Con_Printf(S_COLOR_GRAY"[%s]: SCTP: Oversized\n", sctp->friendlyname); sctp->i.r.toobig = true; //reassembled packet was too large, just corrupt it. } else @@ -3629,7 +3668,7 @@ static void SCTP_Decode(sctp_t *sctp, struct icestate_s *peer, ftenet_connection } } else if (sctp->i.r.ppid == BigLong(SCTP_PPID_DCEP)) - SCTP_DecodeDCEP(sctp, peer, resp); + SCTP_DecodeDCEP(sctp, resp); } } @@ -3681,7 +3720,7 @@ static void SCTP_Decode(sctp_t *sctp, struct icestate_s *peer, ftenet_connection break; default: if (net_ice_debug.ival >= 2) - Con_Printf(S_COLOR_GRAY"[%s]: SCTP: Found unknown init parameter %i||%#x\n", peer->friendlyname, ptype, ptype); + Con_Printf(S_COLOR_GRAY"[%s]: SCTP: Found unknown init parameter %i||%#x\n", sctp->friendlyname, ptype, ptype); break; } p = (void*)((qbyte*)p + ((plen+3)&~3)); @@ -3691,7 +3730,7 @@ static void SCTP_Decode(sctp_t *sctp, struct icestate_s *peer, ftenet_connection { sctp->nextreinit = 0; if (sctp->cookie) - SCTP_Transmit(sctp, peer, NULL, 0); //make sure we send acks occasionally even if we have nothing else to say. + SCTP_Transmit(sctp, NULL, 0); //make sure we send acks occasionally even if we have nothing else to say. } else { @@ -3731,7 +3770,7 @@ static void SCTP_Decode(sctp_t *sctp, struct icestate_s *peer, ftenet_connection //complete. calc the proper crc and send it off. rh->crc = SCTP_Checksum(rh, end-resp); - SCTP_PeerSendPacket(peer, end-resp, rh); + SCTP_PeerSendPacket(sctp, end-resp, rh); } } break; @@ -3765,24 +3804,26 @@ static void SCTP_Decode(sctp_t *sctp, struct icestate_s *peer, ftenet_connection //complete. calc the proper crc and send it off. pongh->crc = SCTP_Checksum(pongh, sizeof(*pongh) + clen); - SCTP_PeerSendPacket(peer, sizeof(*pongh) + clen, pongh); + SCTP_PeerSendPacket(sctp, sizeof(*pongh) + clen, pongh); Z_Free(pongh); } break; // case SCTP_TYPE_PONG: //we don't send pings case SCTP_TYPE_ABORT: - SCTP_ErrorChunk(peer, "Abort", (struct sctp_errorcause_s*)(c+1), clen-sizeof(*c)); - ICE_Set(peer, "state", STRINGIFY(ICE_FAILED)); + SCTP_ErrorChunk(sctp, "Abort", (struct sctp_errorcause_s*)(c+1), clen-sizeof(*c)); + if (sctp->icestate) + ICE_Set(sctp->icestate, "state", STRINGIFY(ICE_FAILED)); break; case SCTP_TYPE_SHUTDOWN: //FIXME. we should send an ack... - ICE_Set(peer, "state", STRINGIFY(ICE_FAILED)); + if (sctp->icestate) + ICE_Set(sctp->icestate, "state", STRINGIFY(ICE_FAILED)); if (net_ice_debug.ival >= 1) - Con_Printf(S_COLOR_GRAY"[%s]: SCTP: Shutdown\n", peer->friendlyname); + Con_Printf(S_COLOR_GRAY"[%s]: SCTP: Shutdown\n", sctp->friendlyname); break; // case SCTP_TYPE_SHUTDOWNACK: //we don't send shutdowns, cos we're lame like that... case SCTP_TYPE_ERROR: //not fatal... - SCTP_ErrorChunk(peer, "Error", (struct sctp_errorcause_s*)(c+1), clen-sizeof(*c)); + SCTP_ErrorChunk(sctp, "Error", (struct sctp_errorcause_s*)(c+1), clen-sizeof(*c)); break; case SCTP_TYPE_COOKIEECHO: if (clen >= sizeof(struct sctp_chunk_s)) @@ -3801,7 +3842,7 @@ static void SCTP_Decode(sctp_t *sctp, struct icestate_s *peer, ftenet_connection //complete. calc the proper crc and send it off. rh->crc = SCTP_Checksum(rh, end-resp); - SCTP_PeerSendPacket(peer, end-resp, rh); + SCTP_PeerSendPacket(sctp, end-resp, rh); sctp->o.writable = true; //channel SHOULD now be open for data. } @@ -3833,19 +3874,183 @@ static void SCTP_Decode(sctp_t *sctp, struct icestate_s *peer, ftenet_connection safedefault: //no idea what this chunk is, just ignore it... if (net_ice_debug.ival >= 1) - Con_Printf(S_COLOR_GRAY"[%s]: SCTP: Unsupported chunk %i\n", peer->friendlyname, c->type); + Con_Printf(S_COLOR_GRAY"[%s]: SCTP: Unsupported chunk %i\n", sctp->friendlyname, c->type); break; } c = (struct sctp_chunk_s*)((qbyte*)c + ((clen+3)&~3)); //next chunk is 4-byte aligned. } if (sctp->i.ackneeded >= 5) - SCTP_Transmit(sctp, peer, NULL, 0); //make sure we send acks occasionally even if we have nothing else to say. + SCTP_Transmit(sctp, NULL, 0); //make sure we send acks occasionally even if we have nothing else to say. //we already made sense of it all. net_message.cursize = 0; } +#if 0 +qboolean SCTP_Handshake(const dtlsfuncs_t *dtlsfuncs, void *dtlsstate, sctp_t **out) +{ + const int myport = ICELITE_SCTP_PORT; + struct cookiedata_s + { + qbyte peerhasfwdtsn; + int overifycode; + int iverifycode; + int ictsn; + int otsn; +// int checkcode; + }; + //if this is an sctp init packet, send a cookie. + //if its an initack then create the new state. + qbyte resp[4096]; + + qbyte *msg = net_message.data; + qbyte *msgend = msg+net_message.cursize; + struct sctp_header_s *h = (struct sctp_header_s*)msg; + struct sctp_chunk_s *c = (struct sctp_chunk_s*)(h+1); + quint16_t clen; + if (net_message.cursize&3) + return false; + if ((qbyte*)c+1 > msgend) + return false; //runt + if (h->dstport != htons(myport)) + return false; //not for us... + clen = BigShort(c->length); + if ((qbyte*)c + ((clen+3)&~3) == msgend) //don't allow multiple chucks + switch(c->type) + { + default: + return false; //not the right kind of packet. + case SCTP_TYPE_COOKIEECHO: + if (clen == sizeof(struct sctp_chunk_s)+sizeof(struct cookiedata_s)) + { + struct cookiedata_s *cookie = (struct cookiedata_s *)(c+1); + sctp_t *sctp; + struct sctp_header_s *rh = (void*)resp; + struct sctp_chunk_s *rack = (void*)(rh+1); + qbyte *end = (void*)(rack+1); + if (h->verifycode == cookie->iverifycode) + if (h->crc == SCTP_Checksum(h, net_message.cursize)) //make sure the crc matches. + { //looks okay. + NET_AdrToString(resp, sizeof(resp), &net_from); + + *out = sctp = Z_Malloc(sizeof(*sctp) + strlen(resp)); + sctp->friendlyname = strcpy((char*)(sctp+1), resp); + sctp->icestate = NULL; + sctp->dtlsfuncs = dtlsfuncs; + sctp->dtlsstate = dtlsstate; + + sctp->myport = h->dstport; + sctp->peerport = h->srcport; + sctp->o.tsn = cookie->otsn; + sctp->i.ctsn = cookie->ictsn; + sctp->o.verifycode = cookie->overifycode; + sctp->i.verifycode = cookie->iverifycode; + sctp->peerhasfwdtsn = cookie->peerhasfwdtsn; + sctp->o.writable = true; //channel SHOULD now be open for data (once it gets the ack anyway). + + //let our peer know too. + rh->srcport = sctp->myport; + rh->dstport = sctp->peerport; + rh->verifycode = sctp->o.verifycode; + rh->crc = 0; + rack->type = SCTP_TYPE_COOKIEACK; + rack->flags = 0; + rack->length = BigShort(sizeof(*rack)); + + //complete. calc the proper crc and send it off. + rh->crc = SCTP_Checksum(rh, end-resp); + SCTP_PeerSendPacket(sctp, end-resp, rh); + return true; + } + } + break; + case SCTP_TYPE_INIT: + if (h->verifycode == 0) //this must be 0 for inits. + if (clen >= sizeof(struct sctp_chunk_init_s)) + if (h->crc == SCTP_Checksum(h, net_message.cursize)) //make sure the crc matches. + { + struct sctp_chunk_init_s *init = (void*)c; + struct { + quint16_t ptype; + quint16_t plen; + } *p = (void*)(init+1); + + struct cookiedata_s cookie = {0}; + + cookie.otsn = rand() ^ (rand()<<16); + Sys_RandomBytes((void*)&cookie.iverifycode, sizeof(cookie.iverifycode)); + cookie.ictsn = BigLong(init->tsn)-1; + cookie.overifycode = init->verifycode; + (void)BigLong(init->arwc); + (void)BigShort(init->numoutstreams); + (void)BigShort(init->numinstreams); + + while ((qbyte*)p+sizeof(*p) <= (qbyte*)c+clen) + { + unsigned short ptype = BigShort(p->ptype); + unsigned short plen = BigShort(p->plen); + switch(ptype) + { + case 32776: //ASCONF + break; + case 49152: + cookie.peerhasfwdtsn = true; + break; + default: + if (net_ice_debug.ival >= 2) + Con_Printf(S_COLOR_GRAY"[%s]: SCTP: Found unknown init parameter %i||%#x\n", NET_AdrToString(resp,sizeof(resp), &net_from), ptype, ptype); + break; + } + p = (void*)((qbyte*)p + ((plen+3)&~3)); + } + + { + struct sctp_header_s *rh = (void*)resp; + struct sctp_chunk_init_s *rinit = (void*)(rh+1); + struct { + quint16_t ptype; + quint16_t plen; + struct cookiedata_s cookie; + } *rinitcookie = (void*)(rinit+1); + struct { + quint16_t ptype; + quint16_t plen; + } *rftsn = (void*)(rinitcookie+1); + qbyte *end = cookie.peerhasfwdtsn?(void*)(rftsn+1):(void*)(rinitcookie+1); + + rh->srcport = h->dstport; + rh->dstport = h->srcport; + rh->verifycode = init->verifycode; + rh->crc = 0; + *rinit = *init; + rinit->chunk.type = SCTP_TYPE_INITACK; + rinit->chunk.flags = 0; + rinit->chunk.length = BigShort(end-(qbyte*)rinit); + rinit->verifycode = cookie.iverifycode; + rinit->arwc = BigLong(65536); + rinit->numoutstreams = init->numoutstreams; + rinit->numinstreams = init->numinstreams; + rinit->tsn = BigLong(cookie.otsn); + rinitcookie->ptype = BigShort(7); + rinitcookie->plen = BigShort(sizeof(*rinitcookie)); + memcpy(&rinitcookie->cookie, &cookie, sizeof(rinitcookie->cookie)); //frankly the contents of the cookie are irrelevant to anything. we've already verified the peer's ice pwd/ufrag stuff as well as their dtls certs etc. + rftsn->ptype = BigShort(49152); + rftsn->plen = BigShort(sizeof(*rftsn)); + + //complete. calc the proper crc and send it off. + rh->crc = SCTP_Checksum(rh, end-resp); + dtlsfuncs->Transmit(dtlsstate, resp, end-resp); + } + return true; + } + break; + } + + return false; +} +#endif + //======================================== #endif @@ -4031,9 +4236,24 @@ qboolean ICE_WasStun(ftenet_connections_t *col) if (con->state == ICE_CONNECTING) ICE_Set(con, "state", STRINGIFY(ICE_CONNECTED)); } + return true; } } } + + //only accept actual responses, not spoofed stuff. + if (stun->magiccookie == BigLong(0x2112a442) + && stun->transactid[0]==col->srflx_tid[0] + && stun->transactid[1]==col->srflx_tid[1] + && stun->transactid[2]==col->srflx_tid[2] + && !NET_CompareAdr(&col->srflx[adr.type!=NA_IP], &adr)) + { + if (col->srflx[adr.type!=NA_IP].type==NA_INVALID) + Con_Printf(S_COLOR_GRAY"Public address reported as %s\n", NET_AdrToString(errmsg, sizeof(errmsg), &adr)); + else + Con_Printf(CON_ERROR"Server reflexive address changed to %s\n", NET_AdrToString(errmsg, sizeof(errmsg), &adr)); + col->srflx[adr.type!=NA_IP] = adr; + } } return true; } @@ -4465,6 +4685,7 @@ qboolean ICE_WasStun(ftenet_connections_t *col) unsigned int tielow = 0; qboolean usecandidate = false; unsigned int priority = 0; + char *lpwd = NULL; #endif char *integritypos = NULL; int error = 0; @@ -4645,6 +4866,7 @@ qboolean ICE_WasStun(ftenet_connections_t *col) if (con->state == ICE_CONNECTING) ICE_Set(con, "state", STRINGIFY(ICE_CONNECTED)); } + lpwd = con->lpwd; } }//otherwise its just an ip check else @@ -4736,13 +4958,13 @@ qboolean ICE_WasStun(ftenet_connections_t *col) MSG_WriteChar(&buf, 0); #ifdef SUPPORT_ICE - if (con) + if (lpwd) { //message integrity is a bit annoying data[2] = ((buf.cursize+4+sizeof(integrity)-20)>>8)&0xff; //hashed header length is up to the end of the hmac attribute data[3] = ((buf.cursize+4+sizeof(integrity)-20)>>0)&0xff; //but the hash is to the start of the attribute's header - CalcHMAC(&hash_sha1, integrity, sizeof(integrity), data, buf.cursize, con->lpwd, strlen(con->lpwd)); + CalcHMAC(&hash_sha1, integrity, sizeof(integrity), data, buf.cursize, lpwd, strlen(lpwd)); MSG_WriteShort(&buf, BigShort(STUNATTR_MESSAGEINTEGRITIY)); MSG_WriteShort(&buf, BigShort(sizeof(integrity))); //sha1 key length SZ_Write(&buf, integrity, sizeof(integrity)); //integrity data @@ -4760,10 +4982,11 @@ qboolean ICE_WasStun(ftenet_connections_t *col) data[3] = ((buf.cursize-20)>>0)&0xff; #ifdef SUPPORT_ICE - TURN_Encapsulate(con, &net_from, data, buf.cursize); -#else - NET_SendPacket(col, buf.cursize, data, &net_from); + if (con) + TURN_Encapsulate(con, &net_from, data, buf.cursize); + else #endif + NET_SendPacket(col, buf.cursize, data, &net_from); return true; } } @@ -4803,7 +5026,7 @@ qboolean ICE_WasStun(ftenet_connections_t *col) net_from = con->qadr; #ifdef HAVE_DTLS if (con->sctp) - SCTP_Decode(con->sctp, con, col); + SCTP_Decode(con->sctp, col); else #endif if (net_message.cursize) @@ -4862,9 +5085,9 @@ neterr_t ICE_SendPacket(size_t length, const void *data, netadr_t *to) return NETERR_DISCONNECTED; #ifdef HAVE_DTLS if (con->sctp) - return SCTP_Transmit(con->sctp, con, data, length); + return SCTP_Transmit(con->sctp, data, length); if (con->dtlsstate) - return SCTP_PeerSendPacket(con, length, data); + return con->dtlsfuncs->Transmit(con->dtlsstate, data, length); #endif if (con->chosenpeer.type != NA_INVALID) return ICE_Transmit(con, data, length); @@ -5047,7 +5270,7 @@ static void FTENET_ICE_SendOffer(ftenet_ice_connection_t *b, int cl, struct ices ice->blockcandidates = false; } } -static void FTENET_ICE_Establish(ftenet_ice_connection_t *b, int cl, struct icestate_s **ret) +static void FTENET_ICE_Establish(ftenet_ice_connection_t *b, const char *peeraddr, int cl, struct icestate_s **ret) { //sends offer struct icestate_s *ice; qboolean usewebrtc; @@ -5062,7 +5285,7 @@ static void FTENET_ICE_Establish(ftenet_ice_connection_t *b, int cl, struct ices else usewebrtc = net_ice_usewebrtc.ival; #endif - ice = *ret = iceapi.Create(b, NULL, b->generic.islisten?NULL:va("/%s", b->gamename), usewebrtc?ICEM_WEBRTC:ICEM_ICE, b->generic.islisten?ICEP_QWSERVER:ICEP_QWCLIENT, !b->generic.islisten); + ice = *ret = iceapi.Create(b, NULL, b->generic.islisten?((peeraddr&&*peeraddr)?va("%s:%i", peeraddr,cl):NULL):va("/%s", b->gamename), usewebrtc?ICEM_WEBRTC:ICEM_ICE, b->generic.islisten?ICEP_QWSERVER:ICEP_QWCLIENT, !b->generic.islisten); if (!*ret) return; //some kind of error?!? @@ -5259,12 +5482,12 @@ handleerror: { b->error = true; if (net_ice_debug.ival) - Con_Printf(S_COLOR_GRAY"[%s]: Broker lost connection: %s\n", b->ice?b->ice->friendlyname:"?", *data?data:""); + Con_Printf(S_COLOR_GRAY"[%s]: Broker host lost connection: %s\n", b->ice?b->ice->friendlyname:"?", *data?data:""); } else if (cl >= 0 && cl < b->numclients) { if (net_ice_debug.ival) - Con_Printf(S_COLOR_GRAY"[%s]: Broker lost connection: %s\n", b->clients[cl].ice?b->clients[cl].ice->friendlyname:"?", *data?data:""); + Con_Printf(S_COLOR_GRAY"[%s]: Broker client lost connection: %s\n", b->clients[cl].ice?b->clients[cl].ice->friendlyname:"?", *data?data:""); if (b->clients[cl].ice) iceapi.Close(b->clients[cl].ice, false); b->clients[cl].ice = NULL; @@ -5291,7 +5514,7 @@ handleerror: } if (cl >= 0 && cl < b->numclients) { - FTENET_ICE_Establish(b, cl, &b->clients[cl].ice); + FTENET_ICE_Establish(b, (len>3)?data:NULL, cl, &b->clients[cl].ice); if (net_ice_debug.ival) Con_Printf(S_COLOR_GRAY"[%s]: New client spotted...\n", b->clients[cl].ice?b->clients[cl].ice->friendlyname:"?"); @@ -5302,7 +5525,7 @@ handleerror: else { // Con_DPrintf("Server found: %s\n", data); - FTENET_ICE_Establish(b, cl, &b->ice); + FTENET_ICE_Establish(b, (len>3)?data:NULL, cl, &b->ice); b->serverid = cl; if (net_ice_debug.ival) @@ -5526,4 +5749,134 @@ void ICE_Init(void) Cvar_Register(&net_ice_debug, "networking"); Cmd_AddCommand("net_ice_show", ICE_Show_f); } + + +#ifdef HAVE_SERVER +void SVC_ICE_Offer(void) +{ //handles an 'ice_offer' udp message from a broker + extern cvar_t net_ice_servers; + struct icestate_s *ice; + static float throttletimer; + char *sdp, *s; + char buf[1400]; + int sz; + char *clientaddr = Cmd_Argv(1); //so we can do ip bans on the client's srflx address + char *brokerid = Cmd_Argv(2); //specific id to identify the pairing on the broker. + netadr_t adr; + if (!sv.state) + return; //err..? + if (net_from.prot != NP_DTLS && net_from.prot != NP_WSS && net_from.prot != NP_TLS) + { //a) dtls provides a challenge. + //b) this contains the caller's ips. We'll be pinging them anyway, but hey. also it'll be too late at this point but it keeps the other side honest. + Con_ThrottlePrintf(&throttletimer, 0, CON_WARNING"%s: ice handshake via %s was unencrypted\n", NET_AdrToString (buf, sizeof(buf), &net_from)); + return; + } + + if (!NET_StringToAdr_NoDNS(clientaddr, 0, &adr)) //no dns-resolution denial-of-service attacks please. + { + Con_ThrottlePrintf(&throttletimer, 0, CON_WARNING"%s: ice handshake specifies bad client address: %s\n", NET_AdrToString (buf, sizeof(buf), &net_from), clientaddr); + return; + } + if (SV_BannedReason(&adr)!=NULL) + { + Con_ThrottlePrintf(&throttletimer, 0, CON_WARNING"%s: ice handshake for %s - banned\n", NET_AdrToString (buf, sizeof(buf), &net_from), clientaddr); + return; + } + + ice = iceapi.Create(NULL, brokerid, clientaddr, ICEM_WEBRTC, ICEP_QWSERVER, false); + if (!ice) + return; //some kind of error?!? + //use the sender as a stun server. FIXME: put server's address in the request instead. + iceapi.Set(ice, "server", va("stun:%s", NET_AdrToString (buf, sizeof(buf), &net_from))); //the sender should be able to act as a stun server for use. should probably just pass who its sending to and call it a srflx anyway, tbh. + + s = net_ice_servers.string; + while((s=COM_Parse(s))) + iceapi.Set(ice, "server", com_token); + + sdp = MSG_ReadString(); + if (!strncmp(sdp, "{\"type\":\"offer\",\"sdp\":\"", 23)) + { //browsers are poo + sdp += 22; + COM_ParseCString(sdp, buf, sizeof(buf), NULL); + sdp = buf; + } + + if (iceapi.Set(ice, "sdpoffer", sdp)) + { + iceapi.Set(ice, "state", STRINGIFY(ICE_CONNECTING)); //skip gathering, just trickle. + + Q_snprintfz(buf, sizeof(buf), "\xff\xff\xff\xff""ice_answer %s", brokerid); + sz = strlen(buf)+1; + if (iceapi.Get(ice, "sdpanswer", buf+sz, sizeof(buf)-sz)) + { + sz += strlen(buf+sz); + + NET_SendPacket(svs.sockets, sz, buf, &net_from); + } + } + + //and because we won't have access to its broker, disconnect it from any persistent state to let it time out. + iceapi.Close(ice, false); +} +void SVC_ICE_Candidate(void) +{ + struct icestate_s *ice; + char *sdp; + char buf[1400]; + char *brokerid = Cmd_Argv(1); //specific id to identify the pairing on the broker. + unsigned int seq = atoi(Cmd_Argv(2)); //their seq, to ack and prevent dupes + unsigned int ack = atoi(Cmd_Argv(3)); //so we don't resend endlessly... *cough* + if (net_from.prot != NP_DTLS && net_from.prot != NP_WSS && net_from.prot != NP_TLS) + { + return; + } + ice = iceapi.Find(NULL, brokerid); + if (!ice) + return; //bad state. lost packet? + + //parse the inbound candidates + for(;;) + { + sdp = MSG_ReadStringLine(); + if (msg_badread || !*sdp) + break; + if (seq++ < ice->u.inseq) + continue; + ice->u.inseq++; + if (!strncmp(sdp, "{\"candidate\":\"", 14)) + { //dewebify + sdp += 13; + buf[0]='a'; + buf[1]='='; + COM_ParseCString(sdp, buf+2, sizeof(buf)-2, NULL); + sdp = buf; + } + iceapi.Set(ice, "sdp", sdp); + } + + while (ack > ice->u.outseq) + { //drop an outgoing candidate line + char *nl = strchr(ice->u.text, '\n'); + if (nl) + { + nl++; + memmove(ice->u.text, nl, strlen(nl)+1); //chop it away. + ice->u.outseq++; + continue; + } + //wut? + if (ack > ice->u.outseq) + ice->u.outseq = ack; //a gap? oh noes! + break; + } + + //check for new candidates to include + while (iceapi.GetLCandidateSDP(ice, buf, sizeof(buf))) + Z_StrCat(&ice->u.text, buf); + + Q_snprintfz(buf, sizeof(buf), "\xff\xff\xff\xff""ice_scand %s %u %u\n%s", brokerid, ice->u.outseq, ice->u.inseq, ice->u.text?ice->u.text:""); + NET_SendPacket(svs.sockets, strlen(buf), buf, &net_from); +} +#endif + #endif diff --git a/engine/common/net_wins.c b/engine/common/net_wins.c index cf92ba247..d39fa96c4 100644 --- a/engine/common/net_wins.c +++ b/engine/common/net_wins.c @@ -142,6 +142,7 @@ cvar_t net_enable_tls = CVARD("net_enable_tls", "0", "If enabled, binary dat #endif #ifdef HAVE_HTTPSV #ifdef SV_MASTER +extern ftenet_connections_t *svm_sockets; cvar_t net_enable_http = CVARD("net_enable_http", "1", "If enabled, tcp ports will accept inbound http clients, potentially serving large files which could distrupt gameplay (This does not affect outgoing http(s) requests)."); cvar_t net_enable_rtcbroker = CVARD("net_enable_rtcbroker", "1", "If 1, tcp ports will accept websocket connections from clients trying to broker direct webrtc connections. This should be low traffic, but might involve a lot of mostly-idle connections."); cvar_t net_enable_websockets = CVARD("net_enable_websockets", "0", "If enabled, tcp ports will accept websocket game clients."); @@ -153,32 +154,34 @@ cvar_t net_enable_websockets = CVARD("net_enable_websockets", "1", "If enabled, #endif #endif #if defined(HAVE_DTLS) -#if defined(HAVE_SERVER) static void QDECL NET_Enable_DTLS_Changed(struct cvar_s *var, char *oldvalue) { - char cert[8192]; - int certsize; - char digest[DIGEST_MAXSIZE]; var->ival = var->value; //set up the default value if (!*var->string) - var->ival = 0; //FIXME: change to 1 then 2 when better tested. + var->ival = 1; //FIXME: change to 1 then 2 when better tested. +#if defined(HAVE_SERVER) if (svs.sockets) { svs.sockets->dtlsfuncs = (var->ival)?DTLS_InitServer():NULL; if (!svs.sockets->dtlsfuncs && var->ival >= 2) Con_Printf("%sUnable to set %s to \"%s\", no DTLS provider available.\n", (var->ival >= 2)?CON_ERROR:CON_WARNING, var->name, var->string); + } - certsize = svs.sockets->dtlsfuncs?svs.sockets->dtlsfuncs->GetPeerCertificate(NULL, QCERT_LOCALCERTIFICATE, cert, sizeof(cert)):-1; + { + char cert[8192]; + int certsize; + char digest[DIGEST_MAXSIZE]; + certsize = (svs.sockets&&svs.sockets->dtlsfuncs)?svs.sockets->dtlsfuncs->GetPeerCertificate(NULL, QCERT_LOCALCERTIFICATE, cert, sizeof(cert)):-1; if (certsize > 0) - InfoBuf_SetStarBlobKey(&svs.info, "*fp", cert, Base64_EncodeBlockURI(digest, CalcHash(&hash_sha1, digest, sizeof(digest), cert, certsize), cert, sizeof(cert))); + InfoBuf_SetStarBlobKey(&svs.info, "*fp", cert, Base64_EncodeBlockURI(digest, CalcHash(&hash_sha2_256, digest, sizeof(digest), cert, certsize), cert, sizeof(cert))); else InfoBuf_SetStarKey(&svs.info, "*fp", ""); } +#endif } cvar_t net_enable_dtls = CVARAFCD("net_enable_dtls", "", "sv_listen_dtls", 0, NET_Enable_DTLS_Changed, "Controls serverside dtls support.\n0: dtls blocked, not advertised.\n1: clientside choice.\n2: used where possible (recommended setting).\n3: disallow non-dtls clients (sv_port_tcp should be eg tls://[::]:27500 to also disallow unencrypted tcp connections)."); -#endif cvar_t dtls_psk_hint = CVARFD("dtls_psk_hint", "", CVAR_NOUNSAFEEXPAND, "For DTLS-PSK handshakes. This specifies the public server identity."); cvar_t dtls_psk_user = CVARFD("dtls_psk_user", "", CVAR_NOUNSAFEEXPAND, "For DTLS-PSK handshakes. This specifies the username to use when the client+server's hints match."); cvar_t dtls_psk_key = CVARFD("dtls_psk_key", "", CVAR_NOUNSAFEEXPAND, "For DTLS-PSK handshakes. This specifies the hexadecimal key which must match between client+server. Will only be used when client+server's hint settings match."); @@ -1681,7 +1684,8 @@ size_t NET_StringToAdr2 (const char *s, int defaultport, netadr_t *a, size_t num a->prot = NP_RTC_TCP; s += 6; } - path = strchr(path, '/'); + + path = strchr(s, '/'); if (path) { if (s == path) //no hostname specified = use default broker (resolving it later) @@ -1692,12 +1696,18 @@ size_t NET_StringToAdr2 (const char *s, int defaultport, netadr_t *a, size_t num a->address.websocketurl[path-s] = 0; } else + { + Con_DPrintf("Path too long\n"); return 0; //too long + } if (pathstart) *pathstart = path; } else + { + Con_DPrintf("No path\n"); return 0; //reject it when there's no path + } return 1; } else if (!strncmp (s, "ws://", 5) || !strncmp (s, "wss://", 6)) @@ -1710,6 +1720,20 @@ size_t NET_StringToAdr2 (const char *s, int defaultport, netadr_t *a, size_t num Q_strncpyz(a->address.websocketurl, s, sizeof(a->address.websocketurl)); return 1; } + else if (*net_ice_broker.string) + { + a->type = NA_INVALID; //not quite right, but w/e. + a->prot = NP_RTC_TLS; + Q_snprintfz(a->address.websocketurl, sizeof(a->address.websocketurl), "/udp/%s", s); +// if (numaddresses < 2) + return 1; + + a++; + a->type = NA_WEBSOCKET; + a->prot = NP_WSS; + Q_snprintfz(a->address.websocketurl, sizeof(a->address.websocketurl), "wss://%s", s); + return 2; + } else { /*code for convienience - no other protocols work anyway*/ @@ -2041,7 +2065,7 @@ int ParsePartialIP(const char *s, netadr_t *a) { if (*s == ':') { - port = strtoul(s+1, &address, 10); + port = htons(strtoul(s+1, &address, 10)); if (*address) //if there was something other than a number there, give up now return 0; break; //end-of-string @@ -2146,6 +2170,49 @@ qboolean NET_StringToAdrMasked (const char *s, qboolean allowdns, netadr_t *a, n return true; } +qboolean NET_StringToAdr_NoDNS(const char *address, int port, netadr_t *out) +{ + int peerbits; + if (*address == '[') + { + char *close = strchr(address+1, ']'); + if (close) + *close = 0; + peerbits = NET_StringToAdr_NoDNS(address+1, 0, out); + if (close) + { + *close = ']'; + if (close[1] == ':') + out->port = htons(atoi(close+2)); + } + return peerbits; + } + else + { + peerbits = ParsePartialIP(address, out); + if (out->type == NA_IP && peerbits == 32) + { + //ignore invalid addresses + if (!out->address.ip[0] && !out->address.ip[1] && !out->address.ip[2] && !out->address.ip[3]) + out->type = NA_INVALID; + } + else if (out->type == NA_IPV6 && peerbits == 128) + { + //ignore invalid addresses + int i; + for (i = 0; i < countof(out->address.ip6); i++) + if (out->address.ip6[i]) + break; + if (i == countof(out->address.ip6)) + out->type = NA_INVALID; + } + else + out->type = NA_INVALID; + + return out->type != NA_INVALID; + } +} + qboolean NET_IsEncrypted(netadr_t *adr) { if (adr->type == NA_LOOPBACK) @@ -3044,7 +3111,7 @@ struct dtlspeer_s struct dtlspeer_s **link; }; -void NET_DTLS_Timeouts(ftenet_connections_t *col) +static void NET_DTLS_Timeouts(ftenet_connections_t *col) { struct dtlspeer_s *peer, **link; if (!col) @@ -3107,7 +3174,7 @@ static neterr_t FTENET_DTLS_DoSendPacket(void *cbctx, const qbyte *data, size_t struct dtlspeer_s *peer = cbctx; return NET_SendPacketCol(peer->col, length, data, &peer->addr); } -qboolean NET_DTLS_Create(ftenet_connections_t *col, netadr_t *to, const dtlscred_t *cred) +qboolean NET_DTLS_Create(ftenet_connections_t *col, netadr_t *to, const dtlscred_t *cred, qboolean outgoing) { extern cvar_t timeout; struct dtlspeer_s *peer; @@ -3124,12 +3191,12 @@ qboolean NET_DTLS_Create(ftenet_connections_t *col, netadr_t *to, const dtlscred peer->addr = *to; peer->col = col; - if (col->islisten) - peer->funcs = DTLS_InitServer(); - else + if (outgoing) peer->funcs = DTLS_InitClient(); + else + peer->funcs = DTLS_InitServer(); if (peer->funcs) - peer->dtlsstate = peer->funcs->CreateContext(cred, peer, FTENET_DTLS_DoSendPacket, col->islisten); + peer->dtlsstate = peer->funcs->CreateContext(cred, peer, FTENET_DTLS_DoSendPacket, !outgoing); peer->timeout = realtime+timeout.value; if (peer->dtlsstate) @@ -3146,6 +3213,8 @@ qboolean NET_DTLS_Create(ftenet_connections_t *col, netadr_t *to, const dtlscred peer = NULL; } } + else + peer->timeout = realtime+timeout.value; return peer!=NULL; } #ifdef HAVE_SERVER @@ -3168,7 +3237,7 @@ static void FTENET_DTLS_Established(void **ctx, void *state) } qboolean NET_DTLS_CheckInbound(ftenet_connections_t *col) { - extern cvar_t timeout, net_enable_dtls; + extern cvar_t timeout; struct dtlspeer_s *peer; netadr_t *from = &net_from; if (from->prot != NP_DGRAM || !net_enable_dtls.ival || !col->dtlsfuncs) @@ -3205,6 +3274,16 @@ static void NET_DTLS_DisconnectPeer(ftenet_connections_t *col, struct dtlspeer_s peer->funcs->DestroyContext(peer->dtlsstate); Z_Free(peer); } +static struct dtlspeer_s *FTENET_DTLS_FindPeer(ftenet_connections_t *col, netadr_t *to) +{ + struct dtlspeer_s *peer; + for (peer = col->dtls; peer; peer = peer->next) + { + if (NET_CompareAdr(&peer->addr, to)) + break; + } + return peer; +} qboolean NET_DTLS_Disconnect(ftenet_connections_t *col, netadr_t *to) { struct dtlspeer_s *peer; @@ -3212,25 +3291,17 @@ qboolean NET_DTLS_Disconnect(ftenet_connections_t *col, netadr_t *to) if (!col || (to->prot != NP_DGRAM && to->prot != NP_DTLS)) return false; n.prot = NP_DGRAM; - for (peer = col->dtls; peer; peer = peer->next) - { - if (NET_CompareAdr(&peer->addr, &n)) - { - NET_DTLS_DisconnectPeer(col, peer); - break; - } - } - return peer?true:false; + peer = FTENET_DTLS_FindPeer(col, &n); + if (!peer) + return false; + NET_DTLS_DisconnectPeer(col, peer); + return true; } static neterr_t FTENET_DTLS_SendPacket(ftenet_connections_t *col, int length, const void *data, netadr_t *to) { struct dtlspeer_s *peer; to->prot = NP_DGRAM; - for (peer = col->dtls; peer; peer = peer->next) - { - if (NET_CompareAdr(&peer->addr, to)) - break; - } + peer = FTENET_DTLS_FindPeer(col, to); to->prot = NP_DTLS; if (peer) return peer->funcs->Transmit(peer->dtlsstate, data, length); @@ -3241,34 +3312,31 @@ static neterr_t FTENET_DTLS_SendPacket(ftenet_connections_t *col, int length, co qboolean NET_DTLS_Decode(ftenet_connections_t *col) { extern cvar_t timeout; - struct dtlspeer_s *peer; - for (peer = col->dtls; peer; peer = peer->next) + struct dtlspeer_s *peer = FTENET_DTLS_FindPeer(col, &net_from); + if (peer) { - if (NET_CompareAdr(&peer->addr, &net_from)) + peer->timeout = realtime+timeout.value; //refresh the timeout if our peer is still alive. + switch(peer->funcs->Received(peer->dtlsstate, &net_message)) { - peer->timeout = realtime+timeout.value; //refresh the timeout if our peer is still alive. - switch(peer->funcs->Received(peer->dtlsstate, &net_message)) - { - case NETERR_DISCONNECTED: - if (col->islisten) - NET_DTLS_DisconnectPeer(col, peer); - net_message.cursize = 0; - break; - case NETERR_NOROUTE: - return false; //not a valid dtls packet. - default: - case NETERR_CLOGGED: - //ate it - net_message.cursize = 0; - break; - case NETERR_SENT: - //we decoded it properly - net_from.prot = NP_DTLS; - break; - } + case NETERR_DISCONNECTED: + if (col->islisten) + NET_DTLS_DisconnectPeer(col, peer); + net_message.cursize = 0; + break; + case NETERR_NOROUTE: + return false; //not a valid dtls packet. + default: + case NETERR_CLOGGED: + //ate it + net_message.cursize = 0; + break; + case NETERR_SENT: + //we decoded it properly net_from.prot = NP_DTLS; - return true; + break; } + net_from.prot = NP_DTLS; + return true; } return false; } @@ -3887,17 +3955,17 @@ neterr_t FTENET_Datagram_SendPacket(ftenet_generic_connection_t *con, int length { char adr[256]; if (ecode==NET_ENETUNREACH&&to->type==NA_IPV6) //ipv6 support STILL sucks too much. don't spam non-developers, its just annoying. - Con_DPrintf("NET_SendPacket(%s) Warning: %i\n", NET_AdrToString (adr, sizeof(adr), to), ecode); + Con_DPrintf(S_COLOR_GRAY"%s - Warning: %i\n", NET_AdrToString (adr, sizeof(adr), to), ecode); #ifdef HAVE_CLIENT else if (ecode == NET_EADDRNOTAVAIL || (ecode==NET_ENETUNREACH&&to->type==NA_IPV6)) - Con_DPrintf("NET_SendPacket(%s) Warning: %i\n", NET_AdrToString (adr, sizeof(adr), to), ecode); + Con_DPrintf(S_COLOR_GRAY"%s - Warning: %i\n", NET_AdrToString (adr, sizeof(adr), to), ecode); else #endif { #ifdef _WIN32 - Con_Printf ("NET_SendPacket(%s) ERROR: %i\n", NET_AdrToString (adr, sizeof(adr), to), ecode); + Con_Printf (S_COLOR_GRAY"%s - ERROR: %i\n", NET_AdrToString (adr, sizeof(adr), to), ecode); #else - Con_Printf ("NET_SendPacket(%s) ERROR: %s\n", NET_AdrToString (adr, sizeof(adr), to), strerror(ecode)); + Con_Printf (S_COLOR_GRAY"%s - ERROR: %s\n", NET_AdrToString (adr, sizeof(adr), to), strerror(ecode)); #endif } } @@ -4282,10 +4350,19 @@ typedef struct ftenet_tcp_stream_s { struct { char resource[64]; - int clientnum; + int clientnum; //low number slots + unsigned int clientseq; //something less reuse-y #ifdef SUPPORT_RTC_ICE struct icestate_s *ice; #endif + //for brokering with a udp-based server. + netadr_t target; + int sends; + char *offer; + char *candidates; + float resendtime; + int candack; //number of received candidates (to avoid dupes) + int outcand; //number of sent candidates (to avoid redundant resends) } webrtc; #endif } ftenet_tcp_stream_t; @@ -4579,9 +4656,19 @@ qboolean FTENET_TCP_HTTPResponse(ftenet_tcp_stream_t *st, httparg_t arg[WCATTR_C body = NULL; } } +#else + else if (!strcmp(name, "favicon.ico")) + { +// Con_Printf("Redirect %s to %s (copyrighted)\n", arg[WCATTR_URL], NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); + resp = va( "HTTP/1.1 302 Found\r\n" + "Location: https://fteqw.org/%s\r\n" + "Content-Type: text/html\r\n" + , "favicon.png"); + body = NULL; + } #endif -#if defined(SV_MASTER) && !defined(HAVE_SERVER) - else if ((st->dlfile=SVM_GenerateIndex(arg[WCATTR_HOST], name, &filetype, query))) +#if defined(SV_MASTER) + else if (svm_sockets && st->con->generic.owner==svm_sockets && (st->dlfile=SVM_GenerateIndex(arg[WCATTR_HOST], name, &filetype, query))) ; #endif #ifdef HAVE_SERVER @@ -4870,7 +4957,7 @@ qboolean FTENET_TCP_HTTPResponse(ftenet_tcp_stream_t *st, httparg_t arg[WCATTR_C void FTENET_TCP_WebRTCServerAssigned(ftenet_tcp_stream_t *list, ftenet_tcp_stream_t *client, ftenet_tcp_stream_t *server) { - qbyte buffer[3]; + qbyte buffer[256]; int trynext = 0; ftenet_tcp_stream_t *o; if (client->webrtc.clientnum < 0) @@ -4889,12 +4976,16 @@ void FTENET_TCP_WebRTCServerAssigned(ftenet_tcp_stream_t *list, ftenet_tcp_strea if (server) { //and tell them both, if the server is actually up + int o = client->remoteaddr.prot; buffer[0] = ICEMSG_NEWPEER; buffer[1] = (client->webrtc.clientnum>>0)&0xff; buffer[2] = (client->webrtc.clientnum>>8)&0xff; // buffer[3] = (client->webrtc.clientnum>>16)&0xff; // buffer[4] = (client->webrtc.clientnum>>24)&0xff; - FTENET_TCP_WebSocket_Splurge(server, WS_PACKETTYPE_BINARYFRAME, buffer, 3); + client->remoteaddr.prot = 0; + NET_BaseAdrToString(buffer+3, sizeof(buffer)-3, &client->remoteaddr); //let the server know who's trying to connect to them. for ip bans. + client->remoteaddr.prot = o; + FTENET_TCP_WebSocket_Splurge(server, WS_PACKETTYPE_BINARYFRAME, buffer, 3+strlen(buffer+3)); buffer[0] = ICEMSG_NEWPEER; buffer[1] = 0xff; @@ -4905,7 +4996,7 @@ void FTENET_TCP_WebRTCServerAssigned(ftenet_tcp_stream_t *list, ftenet_tcp_strea } } -qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcp_connection_t *con, ftenet_tcp_stream_t *st) +static const char *FTENET_TCP_ParseHTTPRequest(ftenet_tcp_connection_t *con, ftenet_tcp_stream_t *st) { char *resp; char adr[256]; @@ -4927,7 +5018,7 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcp_connection_t *con, ftenet_tcp_st "Connection: close\r\n" //let the client know that any pipelining it was doing will have been ignored "\r\n"); VFS_WRITE(st->clientstream, resp, strlen(resp)); - return false; + return "websockets disabled"; } for (i = 0; i < WCATTR_COUNT; i++) @@ -4942,7 +5033,7 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcp_connection_t *con, ftenet_tcp_st "Connection: close\r\n" //let the client know that any pipelining it was doing will have been ignored "\r\n"); VFS_WRITE(st->clientstream, resp, strlen(resp)); - return false; //overflow... + return "overflow"; //overflow... } if (st->inbuffer[i] == ' ' || st->inbuffer[i] == '\t' || st->inbuffer[i] == '\r') { @@ -4965,7 +5056,7 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcp_connection_t *con, ftenet_tcp_st if (st->inbuffer[i] < ' ' && st->inbuffer[i] != '\t') { Con_Printf("http request contained control codes\n"); - return false; + return "bad char"; } arg[attr][alen++] = st->inbuffer[i]; } @@ -5094,7 +5185,7 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcp_connection_t *con, ftenet_tcp_st else { /*just a word on the line on its own. that would be invalid in http*/ - return false; + return "bad header"; } } } @@ -5102,7 +5193,7 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcp_connection_t *con, ftenet_tcp_st if (!headerscomplete) { Con_DPrintf("http header parsing failed\n"); - return false; //the caller said it was complete! something's fucked if we're here + return "bad header"; //the caller said it was complete! something's fucked if we're here } //okay, the above code parsed all the headers that we care about. @@ -5115,10 +5206,10 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcp_connection_t *con, ftenet_tcp_st "Connection: close\r\n" //let the client know that any pipelining it was doing will have been ignored "\r\n"); VFS_WRITE(st->clientstream, resp, strlen(resp)); - Con_Printf("http oversize request\n"); - return false; //can never be completed. + Con_DPrintf("http oversize request\n"); + return "bad header"; //can never be completed. } - return true; + return NULL; } //clients uploading chunked stuff is bad/unsupported. @@ -5128,8 +5219,8 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcp_connection_t *con, ftenet_tcp_st "Connection: close\r\n" //let the client know that any pipelining it was doing will have been ignored "\r\n"); VFS_WRITE(st->clientstream, resp, strlen(resp)); - Con_Printf("http encoded request\n"); - return false; //can't handle the request, so discard the connection + Con_DPrintf("unsupported http encoded request\n"); + return "unsupported"; //can't handle the request, so discard the connection } memmove(st->inbuffer, st->inbuffer+i, st->inlen - (i)); @@ -5159,12 +5250,12 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcp_connection_t *con, ftenet_tcp_st if (cltype == TCPC_WEBRTC_CLIENT||cltype==TCPC_WEBRTC_HOST) { if (!net_enable_rtcbroker.ival) - return false; + return "broker disabled"; } else //TCPC_WEBSOCKETNQ, TCPC_WEBSOCKETB, TCPC_WEBSOCKETU { if (!net_enable_websockets.ival) - return false; + return "websocket clients disabled"; } if (websocketver != 13) @@ -5176,7 +5267,7 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcp_connection_t *con, ftenet_tcp_st "Connection: close\r\n" //let the client know that any pipelining it was doing will have been ignored "\r\n"); VFS_WRITE(st->clientstream, resp, strlen(resp)); - return false; + return "bad header"; } else { @@ -5185,6 +5276,7 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcp_connection_t *con, ftenet_tcp_st unsigned char sha1digest[20]; char *blurgh; char *protoname = ""; + static int clientseq; blurgh = va("%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11", arg[WCATTR_WSKEY]); tobase64(acceptkey, sizeof(acceptkey), sha1digest, CalcHash(&hash_sha1, sha1digest, sizeof(sha1digest), blurgh, strlen(blurgh))); @@ -5220,6 +5312,7 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcp_connection_t *con, ftenet_tcp_st else Q_strncpyz(st->webrtc.resource, arg[WCATTR_URL], sizeof(st->webrtc.resource)); st->webrtc.clientnum = -1; + st->webrtc.clientseq = clientseq++; #ifndef SUPPORT_RTC_ICE if (st->clienttype == TCPC_WEBRTC_CLIENT && !*st->webrtc.resource) failreason = "client did not specify resource type"; @@ -5228,7 +5321,7 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcp_connection_t *con, ftenet_tcp_st if (failreason) { Con_DPrintf("Websocket(%s) request for %s from %s - %s\n", arg[WCATTR_WSPROTO], arg[WCATTR_URL], NET_AdrToString (adr, sizeof(adr), &st->remoteaddr), failreason); - return false; + return failreason; } Con_DPrintf("Websocket request for %s from %s (%s)\n", arg[WCATTR_URL], NET_AdrToString (adr, sizeof(adr), &st->remoteaddr), arg[WCATTR_WSPROTO]); @@ -5242,23 +5335,19 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcp_connection_t *con, ftenet_tcp_st //send the websocket handshake response. VFS_WRITE(st->clientstream, resp, strlen(resp)); - if (st->clienttype == TCPC_WEBRTC_CLIENT && !*st->webrtc.resource) - { //client should be connected to us rather than any impostors. tell it to start its ICE handshake. - net_message_buffer[0] = ICEMSG_NEWPEER; - net_message_buffer[1] = 0xff; - net_message_buffer[2] = 0xff; - FTENET_TCP_WebSocket_Splurge(st, WS_PACKETTYPE_BINARYFRAME, net_message_buffer, 3); - } - else if (st->clienttype == TCPC_WEBRTC_HOST || st->clienttype == TCPC_WEBRTC_CLIENT) + if (st->clienttype == TCPC_WEBRTC_HOST || st->clienttype == TCPC_WEBRTC_CLIENT) { ftenet_tcp_stream_t *o; + + //split the requested resource by protocol/room + char *idstart = strchr(st->webrtc.resource, '/'); + if (!idstart++) + { //MUST have a protocol name + return "no game protocol specified"; + } + if (st->clienttype == TCPC_WEBRTC_HOST) - { //if its a server, then let it know its final resource name - char *idstart = strchr(st->webrtc.resource, '/'); - if (!idstart++) - { //MUST have a protocol name - return false; - } + { if (!*idstart) { //webrtc servers need some unique resource address. lets use their ip+port for now. we should probably be randomising this static unsigned int g; @@ -5275,7 +5364,7 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcp_connection_t *con, ftenet_tcp_st FTENET_TCP_WebSocket_Splurge(st, WS_PACKETTYPE_BINARYFRAME, net_message_buffer, strlen(net_message_buffer)); *st->webrtc.resource = 0; //don't trigger shutdown broadcasts to valid clients. - return false; //conflict! can't have two servers listening on the same url + return "room conflict"; //conflict! can't have two servers listening on the same url } } @@ -5297,14 +5386,65 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcp_connection_t *con, ftenet_tcp_st #endif } else - { //find its server, if we can - for (o = con->tcpstreams; o; o = o->next) - { - if (o->clienttype == TCPC_WEBRTC_HOST && !strcmp(st->webrtc.resource, o->webrtc.resource)) - break; + { //a client looking for a server... + if (!*idstart) + { //that's trying to connect to us... +#ifdef HAVE_SERVER + if (sv.state != ss_dead) + { + net_message_buffer[0] = ICEMSG_NEWPEER; + net_message_buffer[1] = 0xff; + net_message_buffer[2] = 0xff; + FTENET_TCP_WebSocket_Splurge(st, WS_PACKETTYPE_BINARYFRAME, net_message_buffer, 3); + } + else +#endif + return "no local server"; //not running a server. can't honour it. + } + else if (!strncmp(idstart, "udp/", 4)) + { //we don't use StringToAdr to avoid dns lookup stalls (denial of service attacks). should at least work for server browsers. +#if defined(HAVE_DTLS) && defined(SV_MASTER) + struct dtlspeercred_s cred={NULL}; + if (net_enable_dtls.ival) + { + if (!NET_StringToAdr_NoDNS(idstart+4, PORT_QWCLIENT, &st->webrtc.target)) + return "bad target address (names are disallowed)"; +// if (NET_ClassifyAddress(&st->webrtc.target, NULL) <= ASCOPE_LAN) +// return false; //block addresses on the broker's lan. we use non-standard protocols so this should not cause problems while being useful for custom deployments. + +#ifdef SV_MASTER + if (con->generic.owner == svm_sockets) + if (!SVM_FixupServerAddress(&st->webrtc.target, &cred)) + return "target not registered"; //we don't know about this server... + //if the specified credentials, reject the connection if different. +#endif + + //use dtls to contact the server. + if (st->webrtc.target.prot == NP_DGRAM) + st->webrtc.target.prot = NP_DTLS; + if (st->webrtc.target.prot == NP_DTLS) //don't make expensive tcp connections! + NET_EnsureRoute(con->generic.owner, NULL, &cred, &st->webrtc.target, true); + + //we'll sythesise some rdp when we get an offer. + net_message_buffer[0] = ICEMSG_NEWPEER; + net_message_buffer[1] = 0xff; + net_message_buffer[2] = 0xff; + FTENET_TCP_WebSocket_Splurge(st, WS_PACKETTYPE_BINARYFRAME, net_message_buffer, 3); + } + else +#endif + return "rdp-via-udp is not enabled on this broker"; + } + else + { //find its server, if we can + for (o = con->tcpstreams; o; o = o->next) + { + if (o->clienttype == TCPC_WEBRTC_HOST && !strcmp(st->webrtc.resource, o->webrtc.resource)) + break; + } + //and assign it to this client + FTENET_TCP_WebRTCServerAssigned(con->tcpstreams, st, o); } - //and assign it to this client - FTENET_TCP_WebRTCServerAssigned(con->tcpstreams, st, o); } } @@ -5325,14 +5465,17 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcp_connection_t *con, ftenet_tcp_st MSG_WriteByte(&net_message, NQ_NETCHAN_VERSION); con->generic.owner->ReadGamePacket(); } - return true; + return NULL; } } else { if (!net_enable_http.ival) - return false; - return FTENET_TCP_HTTPResponse(st, arg, acceptsgzip); + return "http disabled"; + if (FTENET_TCP_HTTPResponse(st, arg, acceptsgzip)) + return NULL; + else + return "http error"; } } #endif @@ -5385,8 +5528,20 @@ void FTENET_TCP_PrintStatus(ftenet_generic_connection_t *gcon) } } -static qboolean FTENET_TCP_KillStream(ftenet_tcp_connection_t *con, ftenet_tcp_stream_t *st) +static qboolean FTENET_TCP_KillStream(ftenet_tcp_connection_t *con, ftenet_tcp_stream_t *st, const char *reason) { //some sort of error. kill the connection info (will be cleaned up later) + + if (st->clienttype == TCPC_WEBRTC_CLIENT && st->clientstream && !strcmp(st->webrtc.resource, st->webrtc.resource)) + { + qbyte msg[256]; + msg[0] = ICEMSG_PEERLOST; + msg[1] = 0xff; + msg[2] = 0xff; + Q_strncpyz(msg+3, reason, sizeof(msg)-3); + + FTENET_TCP_WebSocket_Splurge(st, WS_PACKETTYPE_BINARYFRAME, msg, 3+strlen(msg+3)); + } + #ifdef HAVE_EPOLL if (st->socketnum != INVALID_SOCKET) epoll_ctl(epoll_fd, EPOLL_CTL_DEL, st->socketnum, NULL); @@ -5394,6 +5549,7 @@ static qboolean FTENET_TCP_KillStream(ftenet_tcp_connection_t *con, ftenet_tcp_s if (st->clientstream) VFS_CLOSE(st->clientstream); st->clientstream = NULL; + st->socketnum = INVALID_SOCKET; if (st->dlfile) VFS_CLOSE(st->dlfile); @@ -5406,12 +5562,13 @@ static qboolean FTENET_TCP_KillStream(ftenet_tcp_connection_t *con, ftenet_tcp_s { if (o->clienttype == TCPC_WEBRTC_HOST && !strcmp(o->webrtc.resource, st->webrtc.resource)) { - qbyte msg[3]; + qbyte msg[256]; msg[0] = ICEMSG_PEERLOST; msg[1] = (st->webrtc.clientnum>>0)&0xff; msg[2] = (st->webrtc.clientnum>>8)&0xff; + Q_strncpyz(msg+3, reason, sizeof(msg)-3); - FTENET_TCP_WebSocket_Splurge(o, WS_PACKETTYPE_BINARYFRAME, msg, 3); + FTENET_TCP_WebSocket_Splurge(o, WS_PACKETTYPE_BINARYFRAME, msg, 3+strlen(msg+3)); break; //should only be one. } } @@ -5423,12 +5580,13 @@ static qboolean FTENET_TCP_KillStream(ftenet_tcp_connection_t *con, ftenet_tcp_s { if (o->clienttype == TCPC_WEBRTC_CLIENT && !strcmp(o->webrtc.resource, st->webrtc.resource)) { - qbyte msg[3]; + qbyte msg[256]; msg[0] = ICEMSG_PEERLOST; msg[1] = (st->webrtc.clientnum>>0)&0xff; msg[2] = (st->webrtc.clientnum>>8)&0xff; + Q_strncpyz(msg+3, reason, sizeof(msg)-3); - FTENET_TCP_WebSocket_Splurge(o, WS_PACKETTYPE_BINARYFRAME, msg, 3); + FTENET_TCP_WebSocket_Splurge(o, WS_PACKETTYPE_BINARYFRAME, msg, 3+strlen(msg+3)); } } #ifdef SV_MASTER @@ -5459,12 +5617,13 @@ static void FTENET_TCP_Flush(ftenet_tcp_connection_t *con, ftenet_tcp_stream_t * }*/ } } -//returns true if we read a game packet (should re-call in this case. -static enum{ - FTETCP_DONE, FTETCP_KILL, FTETCP_RETRY -} FTENET_TCP_ReadStream(ftenet_tcp_connection_t *con, ftenet_tcp_stream_t *st) +//returns a string for why it was killed. or NULL for nothing more to do. +static const char *FTENET_TCP_ReadStream(ftenet_tcp_connection_t *con, ftenet_tcp_stream_t *st) { char adr[MAX_ADR_SIZE]; +restart: //gotos are evil. I am evil. live with it. + if (!st->clientstream) + return NULL; if (st->inlen < sizeof(st->inbuffer)-1) { int ret = VFS_READ(st->clientstream, st->inbuffer+st->inlen, sizeof(st->inbuffer)-1-st->inlen); @@ -5472,7 +5631,7 @@ static enum{ { st->outlen = 0; //don't flush, no point. Con_DPrintf ("tcp peer %s closed connection\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); - return FTETCP_KILL; + return "connection lost"; } st->inlen += ret; } @@ -5481,7 +5640,7 @@ static enum{ { case TCPC_UNKNOWN: if (st->inlen < 6) - return FTETCP_DONE; + return NULL; //so TLS apparently uses a first byte that is always < 64. which is handy to know. if (con->generic.islisten && st->remoteaddr.prot == NP_STREAM && st->clientstream && !((st->inbuffer[0] >= 'a' && st->inbuffer[0] <= 'z') || (st->inbuffer[0] >= 'A' && st->inbuffer[0] <= 'Z'))) @@ -5494,7 +5653,7 @@ static enum{ vfsfile_t *stream = st->clientstream; int (QDECL *realread) (struct vfsfile_s *file, void *buffer, int bytestoread); if (st->inlen > sizeof(net_message_buffer)) - return FTETCP_KILL; //would cause data loss... + return "oversize"; //would cause data loss... realread = stream->ReadBytes; stream->ReadBytes = TLSPromoteRead; memcpy(net_message_buffer, st->inbuffer, st->inlen); @@ -5509,7 +5668,7 @@ static enum{ if (st->inlen < 0) { //okay, something failed... st->inlen = 0; - return FTETCP_KILL; + return "error"; } else { @@ -5518,13 +5677,13 @@ static enum{ } } if (!st->clientstream || net_message.cursize) - return FTETCP_KILL; //failure, or it didn't read all the data that we buffered for it (error instead of forgetting it). + return "tls error"; //failure, or it didn't read all the data that we buffered for it (error instead of forgetting it). if (developer.ival) Con_Printf("promoted peer to tls: %s\n", NET_AdrToString(tmpbuf, sizeof(tmpbuf), &st->remoteaddr)); - return FTETCP_RETRY; //might be a usable packet in there that we now need to make sense of. + goto restart; //might be a usable packet in there that we now need to make sense of. } #endif - return FTETCP_KILL; + return "no tls"; } //check if its a qizmo connection (or rather a general qw-over-tcp connection) @@ -5543,11 +5702,11 @@ static enum{ { //send the qizmo handshake response. if (VFS_WRITE(st->clientstream, "qizmo\n", 6) != 6) - return FTETCP_KILL; //unable to write for some reason. + return "write error"; //unable to write for some reason. } - return FTETCP_DONE; + return NULL; } - return FTETCP_KILL; //not enabled. + return "net_enable_qizmo"; //not enabled. } //check if we have some http-like protocol with a header that ends with two trailing new lines (carrage returns optional, at least here) @@ -5593,7 +5752,7 @@ static enum{ //now try to pass it over MSV_NewNetworkedNode(st->clientstream, st->inbuffer, st->inbuffer+i, st->inlen-i, NET_AdrToString(tmpbuf, sizeof(tmpbuf), &st->remoteaddr)); st->clientstream = NULL; //qtv code took it. - return FTETCP_KILL; + return "node linked"; } else #endif @@ -5614,11 +5773,11 @@ static enum{ { case -2: VFS_PUTS(st->clientstream, "QTVSV 1\n" "PERROR: net_enable_qtv is disabled on this server\n\n"); - return FTETCP_KILL; + return "net_enable_qtv disabled"; case -1: //error - return FTETCP_KILL; + return "error"; case 0: //retry - return FTETCP_DONE; + return NULL; case 1: //accepted #ifdef HAVE_EPOLL //the tcp connection will now be handled by the dedicated qtv code rather than us. @@ -5628,19 +5787,21 @@ static enum{ st->epoll.Polled = NULL; #endif st->clientstream = NULL; //qtv code took it. - return FTETCP_KILL; + return "qtv client"; } } else #endif { #ifdef HAVE_HTTPSV - if (FTENET_TCP_ParseHTTPRequest(con, st)) - return FTETCP_RETRY; + const char *err = FTENET_TCP_ParseHTTPRequest(con, st); + if (!err) + goto restart; + return err; #else Con_DPrintf ("Unknown TCP handshake from %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); + return "unsupported handshake"; #endif - return FTETCP_KILL; } } else @@ -5648,11 +5809,11 @@ static enum{ //they splurged too much data and we don't even know what they were //either way we're expecting a request header in our buffer that can never be completed if (st->inlen >= sizeof(st->inbuffer)-1) - return FTETCP_KILL; + return "unknown"; } } - return FTETCP_DONE; + return NULL; #ifdef HAVE_HTTPSV case TCPC_HTTPCLIENT: /*try and keep it flushed*/ @@ -5672,25 +5833,25 @@ static enum{ st->clienttype = TCPC_UNKNOWN; //wait for the next request (could potentially be a websocket connection) Con_DPrintf ("Outgoing file transfer complete\n"); if (st->httpstate.connection_close) - return FTETCP_KILL; + return "complete"; } FTENET_TCP_Flush(con, st); } - return FTETCP_DONE; + return NULL; #endif case TCPC_QIZMO: if (st->inlen < 2) - return FTETCP_DONE; + return NULL; net_message.cursize = BigShort(*(short*)st->inbuffer); if (net_message.cursize >= sizeof(net_message_buffer) ) { Con_TPrintf ("Warning: Oversize packet from %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); - return FTETCP_KILL; + return "oversize"; } if (net_message.cursize+2 > st->inlen) { //not enough buffered to read a packet out of it. - return FTETCP_DONE; + return NULL; } memcpy(net_message_buffer, st->inbuffer+2, net_message.cursize); @@ -5704,7 +5865,7 @@ static enum{ net_from_connection = &con->generic; con->generic.owner->ReadGamePacket(); - return FTETCP_RETRY; + goto restart; #ifdef HAVE_HTTPSV case TCPC_WEBSOCKETU: case TCPC_WEBSOCKETB: @@ -5721,7 +5882,7 @@ static enum{ if (ctrl & 0x7000) { Con_Printf ("%s: reserved bits set\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); - return FTETCP_KILL; + return "reserved"; } if ((ctrl & 0x7f) == 127) { @@ -5730,7 +5891,7 @@ static enum{ if (sizeof(ullpaylen) < 8) { Con_Printf ("%s: payload frame too large\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); - return FTETCP_KILL; + return "oversize"; } else { @@ -5748,12 +5909,12 @@ static enum{ if (ullpaylen < 0x10000) { Con_Printf ("%s: payload size (%"PRIu64") encoded badly\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr), ullpaylen); - return FTETCP_KILL; + return "corrupt"; } if (ullpaylen > 0x40000) { Con_Printf ("%s: payload size (%"PRIu64") is abusive\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr), ullpaylen); - return FTETCP_KILL; + return "oversize"; } paylen = ullpaylen; payoffs += 8; @@ -5769,7 +5930,7 @@ static enum{ if (paylen < 126) { Con_Printf ("%s: payload size encoded badly\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); - return FTETCP_KILL; + return "corrupt"; } payoffs += 2; } @@ -5794,7 +5955,7 @@ static enum{ if (payoffs + paylen >= sizeof(st->inbuffer)-1) { Con_TPrintf ("Warning: Oversize packet from %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); - return FTETCP_KILL; + return "oversize"; } break; } @@ -5814,7 +5975,7 @@ static enum{ { case WS_PACKETTYPE_CONTINUATION: /*continuation*/ Con_Printf ("websocket continuation frame from %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); - return FTETCP_KILL; //can't handle these. + return "unsupported"; //can't handle these. case WS_PACKETTYPE_TEXTFRAME: /*text frame*/ // Con_Printf ("websocket text frame from %s\n", NET_AdrToString (adr, sizeof(adr), st->remoteaddr)); { @@ -5855,7 +6016,7 @@ static enum{ if (net_message.cursize+8 >= sizeof(net_message_buffer) ) { Con_TPrintf ("Warning: Oversize packet from %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); - return FTETCP_KILL; + return "oversize"; } #ifdef SUPPORT_RTC_ICE if (st->clienttype == TCPC_WEBRTC_CLIENT && !*st->webrtc.resource) @@ -5888,21 +6049,42 @@ static enum{ } else if ((st->clienttype == TCPC_WEBRTC_CLIENT || st->clienttype == TCPC_WEBRTC_HOST) && paylen >= 3) { //we're brokering a client+server. all messages should be unicasts between a client and its host, matched by resource. - ftenet_tcp_stream_t *o; - short clnum = (st->inbuffer[payoffs+1]<<0)|(st->inbuffer[payoffs+2]<<8); - int type = (st->clienttype != TCPC_WEBRTC_CLIENT)?TCPC_WEBRTC_CLIENT:TCPC_WEBRTC_HOST; - for (o = con->tcpstreams; o; o = o->next) - { - if (o->clienttype == type && clnum == o->webrtc.clientnum && !strcmp(o->webrtc.resource, st->webrtc.resource)) + if (st->webrtc.target.type != NA_INVALID && st->clienttype==TCPC_WEBRTC_CLIENT) + { //if the server is a udp one, we need to buffer some stuff to handle resends over a dtls connection that still has to be established. + if (st->inbuffer[payoffs] == ICEMSG_OFFER) { - st->inbuffer[payoffs+1] = (st->webrtc.clientnum>>0)&0xff; - st->inbuffer[payoffs+2] = (st->webrtc.clientnum>>8)&0xff; - FTENET_TCP_WebSocket_Splurge(o, WS_PACKETTYPE_BINARYFRAME, st->inbuffer+payoffs, paylen); - break; + BZ_Free(st->webrtc.offer); + st->webrtc.offer = BZF_Malloc(paylen-3+1); + memcpy(st->webrtc.offer, st->inbuffer+payoffs+3, paylen-3); + st->webrtc.offer[paylen-3] = 0; + st->webrtc.resendtime = FLT_MIN; + } + else if (st->inbuffer[payoffs] == ICEMSG_CANDIDATE && paylen > 3) + { + Z_StrCatLen(&st->webrtc.candidates, st->inbuffer+payoffs+3, paylen-3); + if ((st->inbuffer+payoffs+3)[paylen-3-1]!= '\n') + Z_StrCat(&st->webrtc.candidates, "\n"); + st->webrtc.resendtime = FLT_MIN; } } - if (!o) - Con_DPrintf("Unable to relay\n"); + else + { //forward it to the other side. much easier with tcp. + ftenet_tcp_stream_t *o; + short clnum = (st->inbuffer[payoffs+1]<<0)|(st->inbuffer[payoffs+2]<<8); + int type = (st->clienttype != TCPC_WEBRTC_CLIENT)?TCPC_WEBRTC_CLIENT:TCPC_WEBRTC_HOST; + for (o = con->tcpstreams; o; o = o->next) + { + if (o->clienttype == type && clnum == o->webrtc.clientnum && !strcmp(o->webrtc.resource, st->webrtc.resource)) + { + st->inbuffer[payoffs+1] = (st->webrtc.clientnum>>0)&0xff; + st->inbuffer[payoffs+2] = (st->webrtc.clientnum>>8)&0xff; + FTENET_TCP_WebSocket_Splurge(o, WS_PACKETTYPE_BINARYFRAME, st->inbuffer+payoffs, paylen); + break; + } + } + if (!o) + Con_DPrintf("Unable to relay\n"); + } net_message.cursize = 0; } else @@ -5922,11 +6104,11 @@ static enum{ break; case WS_PACKETTYPE_CLOSE: /*connection close*/ Con_Printf ("websocket closure %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); - return FTETCP_KILL; + return "drop"; //they're about to drop anyway. case WS_PACKETTYPE_PING: /*ping*/ // Con_Printf ("websocket ping from %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); if (FTENET_TCP_WebSocket_Splurge(st, WS_PACKETTYPE_PONG, st->inbuffer+payoffs, paylen) != NETERR_SENT) - return FTETCP_KILL; + return "write error"; break; case WS_PACKETTYPE_PONG: /*pong*/ st->timeouttime = Sys_DoubleTime() + 30; @@ -5935,7 +6117,7 @@ static enum{ break; default: Con_Printf ("Unsupported websocket opcode (%i) from %s\n", (ctrl>>8) & 0xf, NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); - return FTETCP_KILL; + return "unsupported"; } memmove(st->inbuffer, st->inbuffer+payoffs + paylen, st->inlen - (payoffs + paylen)); @@ -5949,41 +6131,124 @@ static enum{ net_from.connum = con->generic.connum; net_from_connection = &con->generic; con->generic.owner->ReadGamePacket(); - return FTETCP_RETRY; + goto restart; } } - return FTETCP_DONE; + return NULL; #endif } - return FTETCP_DONE; + return NULL; } +#ifdef SV_MASTER +static qboolean FTENET_TCP_GetPacket(ftenet_generic_connection_t *gcon); +void FTENET_TCP_ICEResponse(ftenet_connections_t *col, int type, const char *cid, const char *sdp) +{ + unsigned int id, connum; + ftenet_tcp_connection_t *con; + ftenet_tcp_stream_t *o; + + cid = COM_Parse(cid); + id = strtoul(com_token, NULL, 16); + connum = ((id>>16)&0xffff)-1; + id=(short)(id&0xffff); + if (connum >= countof(col->conn) || !col->conn[connum] || col->conn[connum]->GetPacket != FTENET_TCP_GetPacket) + return; + con = (ftenet_tcp_connection_t*)col->conn[connum]; + + for (o = con->tcpstreams; o; o = o->next) + { + if (o->clienttype != TCPC_WEBRTC_CLIENT || o->webrtc.target.type==NA_INVALID || o->webrtc.clientnum != id) + continue; + if (NET_CompareAdr(&net_from, &o->webrtc.target)) + { + char msg[1400]; + Z_Free(o->webrtc.offer); //we got the answer. can stop trying to spam it now. + o->webrtc.offer = NULL; + + if (type == ICEMSG_CANDIDATE) + { + const char *nc = o->webrtc.candidates; + char seq, ack; + cid = COM_Parse(cid); + seq = atoi(com_token); + cid = COM_Parse(cid); + ack = atoi(com_token); + + while (nc && o->webrtc.outcand < ack) + { //server saw one of our candidate lines. stop respamming that one. + o->webrtc.outcand++; + + for (; *nc && *nc != '\n'; nc++) + ; + if (*nc != '\n') + break; //getting exploited... + nc++; //skip the nl + } + if (!nc || !*nc) + { + Z_Free(o->webrtc.candidates); //all acked... + o->webrtc.candidates = NULL; + } + else if (nc != o->webrtc.candidates) + memmove(o->webrtc.candidates, nc, strlen(nc)+1); + o->webrtc.outcand = ack; //in case the server just wanted to drop some. + + while(*sdp) + { + for (nc = sdp; *nc && *nc != '\n'; nc++) + ; + if (*nc != '\n') + break; //getting exploited... + nc++; //skip the nl. + if (seq++ < o->webrtc.candack) + ; //already saw this line + else + { //new. yay reliables... + msg[0] = type; + msg[1] = 0xff; + msg[2] = 0xff; + memcpy(msg+3, sdp, nc-sdp); + FTENET_TCP_WebSocket_Splurge(o, WS_PACKETTYPE_BINARYFRAME, msg, 3+nc-sdp); + o->webrtc.candack=seq; + } + + sdp = nc; + } + } + else + { + msg[0] = type; + msg[1] = 0xff; + msg[2] = 0xff; + Q_strncpyz(msg+3, sdp, sizeof(msg)); + FTENET_TCP_WebSocket_Splurge(o, WS_PACKETTYPE_BINARYFRAME, msg, 3+strlen(msg+3)); + } + break; + } + } +} +#endif + #ifdef HAVE_EPOLL static void FTENET_TCP_Polled(epollctx_t *ctx, unsigned int events) { ftenet_tcp_stream_t *st = NULL; + const char *err; st = (ftenet_tcp_stream_t *)((qbyte*)ctx - ((qbyte*)&st->epoll-(qbyte*)st)); - for(;st->clientstream;) - { - switch(FTENET_TCP_ReadStream(st->con, st)) - { - case FTETCP_RETRY: - continue; - case FTETCP_KILL: - FTENET_TCP_KillStream(st->con, st); - return; - case FTETCP_DONE: - FTENET_TCP_Flush(st->con, st); - return; - } - } + + err = FTENET_TCP_ReadStream(st->con, st); + if (err) + FTENET_TCP_KillStream(st->con, st, err); + else + FTENET_TCP_Flush(st->con, st); } #endif -qboolean FTENET_TCP_GetPacket(ftenet_generic_connection_t *gcon) +static qboolean FTENET_TCP_GetPacket(ftenet_generic_connection_t *gcon) { ftenet_tcp_connection_t *con = (ftenet_tcp_connection_t*)gcon; - int ret; +// int ret; char adr[MAX_ADR_SIZE]; struct sockaddr_qstorage from; int fromlen; @@ -6019,14 +6284,13 @@ qboolean FTENET_TCP_GetPacket(ftenet_generic_connection_t *gcon) } //due to the above checks about invalid sockets, the socket is always open for st below. - if (st->timeouttime < timeval) { #ifdef HAVE_HTTPSV if (!st->pinging && (st->clienttype==TCPC_WEBRTC_CLIENT||st->clienttype==TCPC_WEBRTC_HOST) && *st->webrtc.resource) { //ping broker clients. there usually shouldn't be any data flow to keep it active otherwise. st->timeouttime = timeval + 30; - st->pinging = true; + st->pinging = true; //cleared on ack. FTENET_TCP_WebSocket_Splurge(st, WS_PACKETTYPE_PING, "ping", 4); } @@ -6034,21 +6298,93 @@ qboolean FTENET_TCP_GetPacket(ftenet_generic_connection_t *gcon) #endif { Con_DPrintf ("tcp peer %s timed out\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); - FTENET_TCP_KillStream(con, st); + FTENET_TCP_KillStream(con, st, "timeout"); continue; } } for(;st->clientstream;) { - ret = FTENET_TCP_ReadStream(con, st); - if (ret == FTETCP_RETRY) - continue; - else if (ret == FTETCP_KILL) - FTENET_TCP_KillStream(con, st); + const char *err = FTENET_TCP_ReadStream(con, st); + if (err) + FTENET_TCP_KillStream(con, st, err); break; } FTENET_TCP_Flush(con, st); + + if (st->clienttype==TCPC_WEBRTC_CLIENT && st->webrtc.target.type!=NA_INVALID && st->webrtc.resendtime < timeval && st->clientstream) + { + if (st->webrtc.offer) + { + static struct netprim_s prim={0}; + sizebuf_t msg; + char adr[256]; + netproto_t o = st->remoteaddr.prot; + + if (st->webrtc.sends > 5) + { + if (st->webrtc.sends > 6) + { + FTENET_TCP_KillStream(con, st, "Too many resends"); + continue; + } + st->webrtc.resendtime = timeval + 3; + st->webrtc.sends++; + continue; + } + + st->remoteaddr.prot = 0; //no prefixes! + NET_BaseAdrToString(adr, sizeof(adr), &st->remoteaddr); //let the server know who's trying to connect to them. for ip bans. + st->remoteaddr.prot = o; + + MSG_BeginWriting(&msg, prim, net_message_buffer, sizeof(net_message_buffer)); + MSG_WriteLong(&msg, ~0); + MSG_WriteString(&msg, va("ice_offer %s %x:%x", adr, (con->generic.connum<<16)|(quint16_t)st->webrtc.clientnum, st->webrtc.clientseq)); + MSG_WriteString(&msg, st->webrtc.offer); + safeswitch (NET_SendPacket(con->generic.owner, msg.cursize, msg.data, &st->webrtc.target)) + { + case NETERR_CLOGGED: //dtls still connecting, or just unable to send... + break; //don't update resend timer.. + case NETERR_SENT: + st->webrtc.sends++; + st->webrtc.resendtime = timeval + 1; + break; + case NETERR_NOROUTE: + case NETERR_DISCONNECTED: + case NETERR_MTU: + safedefault: + FTENET_TCP_KillStream(con, st, "target not reachable"); + break; + } + } + else if (st->webrtc.candidates) + { //hopefully the server will have a proper public address and so won't need this... but just in case... + static struct netprim_s prim={0}; + sizebuf_t msg; + + MSG_BeginWriting(&msg, prim, net_message_buffer, sizeof(net_message_buffer)); + MSG_WriteLong(&msg, ~0); + MSG_WriteString(&msg, va("ice_ccand %x:%x %i %i", (con->generic.connum<<16)|(quint16_t)st->webrtc.clientnum, st->webrtc.clientseq, st->webrtc.outcand, st->webrtc.candack)); + MSG_WriteString(&msg, st->webrtc.candidates); + safeswitch (NET_SendPacket(con->generic.owner, msg.cursize, msg.data, &st->webrtc.target)) + { + case NETERR_CLOGGED: //unable to send... + break; //don't update resend timer so we don't lose too much time + case NETERR_SENT: + st->webrtc.resendtime = timeval + 1; + st->webrtc.sends++; + break; + case NETERR_NOROUTE: + case NETERR_DISCONNECTED: + case NETERR_MTU: + safedefault: + FTENET_TCP_KillStream(con, st, "target not reachable"); + break; + } + } + else + st->webrtc.resendtime = timeval + 30; + } } if (con->generic.thesocket != INVALID_SOCKET && con->active < 256) @@ -7225,7 +7561,7 @@ static void FTENET_WebRTC_Heartbeat(ftenet_websocket_connection_t *b) //called from the javascript when there was some ice event. just forwards over the broker connection. static void FTENET_WebRTC_Callback(void *ctxp, int ctxi, int/*enum icemsgtype_s*/ evtype, const char *data) { - ftenet_websocket_connection_t *wcs = ctxp; + ftenet_websocket_connection_t *wsc = ctxp; size_t dl = strlen(data); qbyte *o = net_message_buffer; *o++ = evtype; @@ -7234,7 +7570,7 @@ static void FTENET_WebRTC_Callback(void *ctxp, int ctxi, int/*enum icemsgtype_s* memcpy(o, data, dl); o+=dl; //Con_Printf("To Broker: %i %i %s\n", evtype, ctxi, data); - emscriptenfte_ws_send(wcs->brokersock, net_message_buffer, o-net_message_buffer); + emscriptenfte_ws_send(wsc->brokersock, net_message_buffer, o-net_message_buffer); } static int FTENET_WebRTC_Create(qboolean initiator, ftenet_websocket_connection_t *wsc, int clid) { @@ -7362,7 +7698,17 @@ static qboolean FTENET_WebRTC_GetPacket(ftenet_generic_connection_t *gcon) } } - net_message.cursize = emscriptenfte_ws_recv(wsc->brokersock, net_message_buffer, sizeof(net_message_buffer)); + if (wsc->brokersock == INVALID_SOCKET) + net_message.cursize = 0; + else + { + net_message.cursize = emscriptenfte_ws_recv(wsc->brokersock, net_message_buffer, sizeof(net_message_buffer)); + if (net_message.cursize < 0) + { + emscriptenfte_ws_close(wsc->brokersock); + wsc->brokersock = INVALID_SOCKET; //error! + } + } if (net_message.cursize > 0) { int cmd; @@ -7382,7 +7728,7 @@ static qboolean FTENET_WebRTC_GetPacket(ftenet_generic_connection_t *gcon) if (cl == -1) { wsc->failed = true; -// Con_Printf("Broker closing connection: %s\n", MSG_ReadString()); + Con_Printf("Broker closing connection: %s\n", MSG_ReadString()); } else if (cl >= 0 && cl < wsc->numclients) { @@ -7490,7 +7836,11 @@ static neterr_t FTENET_WebRTC_SendPacket(ftenet_generic_connection_t *gcon, int if (NET_CompareAdr(to, &wsc->remoteadr)) { if (wsc->datasock == INVALID_SOCKET) + { + if (wsc->brokersock == INVALID_SOCKET) + return NETERR_DISCONNECTED; //no broker nor active data channel. its dead jim. return NETERR_CLOGGED; //we're still waiting for the broker to give us a server... or for a server to become available. + } else { if (emscriptenfte_ws_send(wsc->datasock, data, length) <= 0) @@ -7538,6 +7888,7 @@ static int FTENET_WebRTC_Establish(const char *address, const char *type) int i; char url[512]; char cleanaddress[512]; + char *udp=""; char *pre[] = { "wss://", "ices://", "rtcs://", "tls://", "ws://", "ice://", "rtc://", "tcp://"}; @@ -7557,7 +7908,25 @@ static int FTENET_WebRTC_Establish(const char *address, const char *type) if (*address == '/') { path = address+1; - + address = NULL; + } + else + { + path = strchr(address, '/'); + if (!path) + { + if (i<0) + { + udp = "udp/"; + path = address; + address = NULL; + } + else + path = ""; + } + } + if (!address) + { address = net_ice_broker.string; for (i = countof(pre); i --> 0; ) { @@ -7568,20 +7937,17 @@ static int FTENET_WebRTC_Establish(const char *address, const char *type) break; } } - } - else - { - path = strchr(address, '/'); - if (!path) - path = ""; + if (i<0) + i = 0; //default to the first one... wss... + if (!*address) + return INVALID_SOCKET; } Q_strncpyz(cleanaddress, address, sizeof(cleanaddress)); c = strchr(cleanaddress, '/'); if (c) *c = 0; COM_Parse(com_protocolname.string); - Q_snprintfz(url, sizeof(url), "%s%s/%s/%s", pre[i], cleanaddress, com_token, path); - + Q_snprintfz(url, sizeof(url), "%s%s/%s/%s%s", pre[i], cleanaddress, com_token, udp, path); return emscriptenfte_ws_connect(url, type); } @@ -8112,6 +8478,10 @@ void NET_ReadPackets (ftenet_connections_t *collection) collection->bytesout = 0; collection->timemark = ctime; } + +#ifdef HAVE_DTLS + NET_DTLS_Timeouts(collection); +#endif } int NET_LocalAddressForRemote(ftenet_connections_t *collection, netadr_t *remote, netadr_t *local, int idx) @@ -8230,7 +8600,7 @@ neterr_t NET_SendPacket (ftenet_connections_t *collection, int length, const voi return NET_SendPacketCol (collection, length, data, to); } -qboolean NET_EnsureRoute(ftenet_connections_t *collection, char *routename, const struct dtlspeercred_s *peerinfo, netadr_t *adr) +qboolean NET_EnsureRoute(ftenet_connections_t *collection, char *routename, const struct dtlspeercred_s *peerinfo, netadr_t *adr, qboolean outgoing) { switch(adr->prot) { @@ -8243,12 +8613,12 @@ qboolean NET_EnsureRoute(ftenet_connections_t *collection, char *routename, cons case NP_DTLS: #ifdef HAVE_DTLS adr->prot = NP_DGRAM; - if (NET_EnsureRoute(collection, routename, peerinfo, adr)) + if (NET_EnsureRoute(collection, routename, peerinfo, adr, outgoing)) { dtlscred_t cred; memset(&cred, 0, sizeof(cred)); cred.peer = *peerinfo; - if (NET_DTLS_Create(collection, adr, &cred)) + if (NET_DTLS_Create(collection, adr, &cred, outgoing)) { adr->prot = NP_DTLS; return true; @@ -8400,7 +8770,7 @@ enum addressscope_e NET_ClassifyAddress(netadr_t *adr, const char **outdesc) #define MAXADDRESSES 64 void NET_PrintAddresses(ftenet_connections_t *collection) { - int i; + int i, j; char adrbuf[MAX_ADR_SIZE]; int m; qboolean shown = false; @@ -8409,8 +8779,9 @@ void NET_PrintAddresses(ftenet_connections_t *collection) int flags[sizeof(addr)/sizeof(addr[0])]; const char *params[sizeof(addr)/sizeof(addr[0])]; qboolean warn = true; - static const char *scopes[] = {"process", "local", "link", "lan", "net"}; + static const char *scopes[] = {S_COLOR_GRAY"process", S_COLOR_GRAY"local", S_COLOR_GRAY"link", S_COLOR_GRAY"lan", "net"}; const char *desc; + char *fp, *scheme; if (!collection) return; @@ -8436,7 +8807,14 @@ void NET_PrintAddresses(ftenet_connections_t *collection) { shown = true; warn = false; - if ((addr[i].prot == NP_RTC_TCP || addr[i].prot == NP_RTC_TLS) && params[i]) + for (j = 0; j < countof(collection->srflx); j++) + { + if (collection->srflx[j].type!=NA_INVALID && NET_CompareAdr(&collection->srflx[j], &addr[i])) + break; + } + if (m < countof(collection->srflx)) + ; //gonna print it later anyway. + else if ((addr[i].prot == NP_RTC_TCP || addr[i].prot == NP_RTC_TLS) && params[i]) { if (addr[i].type == NA_INVALID) Con_TPrintf("%s address (%s): /%s\n", scopes[scope], con[i]->name, params[i]); @@ -8451,6 +8829,30 @@ void NET_PrintAddresses(ftenet_connections_t *collection) } } + fp = ""; + scheme = ""; +#if defined(HAVE_SERVER) && defined(HAVE_DTLS) + if (collection == svs.sockets) + { + fp = InfoBuf_ValueForKey(&svs.info, "*fp"); + if (*fp) + fp = va(S_COLOR_GRAY"?fp=%s", fp); + if (*COM_Parse(fs_manifest->schemes)) + scheme = va(S_COLOR_GRAY"%s://", com_token); + } +#endif + //show any master-reflexive addresses that were not above. + for (m = 0; m < countof(collection->srflx); m++) + { + if (collection->srflx[m].type == NA_INVALID) + continue; + if (collection->srflx[m].connum && collection->srflx[m].connum-1u < countof(collection->conn) && collection->conn[collection->srflx[m].connum-1]) + Con_TPrintf("reflexive address (%s): %s"S_COLOR_WHITE"%s%s\n", collection->conn[collection->srflx[m].connum-1]->name, scheme, NET_AdrToString(adrbuf, sizeof(adrbuf), &collection->srflx[m]), fp); + else + Con_TPrintf("reflexive address: %s"S_COLOR_WHITE"%s%s\n", scheme, NET_AdrToString(adrbuf, sizeof(adrbuf), &collection->srflx[m]), fp); + warn = false; + } + if (warn) Con_TPrintf("net address: no public addresses\n"); } @@ -9087,6 +9489,9 @@ void NET_Init (void) Cvar_Register(&net_enable_rtcbroker, "networking"); #endif #endif +#ifdef HAVE_DTLS + Cvar_Register(&net_enable_dtls, "networking"); +#endif @@ -9304,9 +9709,6 @@ void SVNET_RegisterCvars(void) // Cvar_Register (&sv_port_unix, "networking"); #endif -#if defined(HAVE_DTLS) && defined(HAVE_SERVER) - Cvar_Register (&net_enable_dtls, "networking"); -#endif #ifdef HAVE_DTLS Cvar_Register (&dtls_psk_hint, "networking"); Cvar_Register (&dtls_psk_user, "networking"); diff --git a/engine/common/netinc.h b/engine/common/netinc.h index 4b1531239..f9fc11038 100644 --- a/engine/common/netinc.h +++ b/engine/common/netinc.h @@ -292,8 +292,8 @@ typedef struct void (QDECL *AddRCandidateInfo)(struct icestate_s *con, struct icecandinfo_s *cand); //stuff that came from the peer. void (QDECL *Close)(struct icestate_s *con, qboolean force); //bye then. void (QDECL *CloseModule)(void *module); //closes all unclosed connections, with warning. -// struct icestate_s *(QDECL *Find)(void *module, const char *conname); qboolean (QDECL *GetLCandidateSDP)(struct icestate_s *con, char *out, size_t valuesize); //retrieves candidates that need reporting to the peer. + struct icestate_s *(QDECL *Find)(void *module, const char *conname); } icefuncs_t; extern icefuncs_t iceapi; extern cvar_t net_ice_broker; @@ -435,6 +435,9 @@ typedef struct ftenet_connections_s size_t cursize; qbyte data[1]; } *delayed_packets; + + netadr_t srflx[2]; //ipv4, ipv6 + unsigned int srflx_tid[3]; //to verify the above. } ftenet_connections_t; void ICE_Tick(void); diff --git a/engine/common/zone.c b/engine/common/zone.c index af974267b..51e5471a8 100644 --- a/engine/common/zone.c +++ b/engine/common/zone.c @@ -254,6 +254,16 @@ char *Z_StrDupf(const char *format, ...) return string; } +void Z_StrCatLen(char **ptr, const char *append, size_t newlen) +{ + size_t oldlen = *ptr?strlen(*ptr):0; + char *newptr = BZ_Malloc(oldlen+newlen+1); + memcpy(newptr, *ptr, oldlen); + memcpy(newptr+oldlen, append, newlen); + newptr[oldlen+newlen] = 0; + BZ_Free(*ptr); + *ptr = newptr; +} void Z_StrCat(char **ptr, const char *append) { size_t oldlen = *ptr?strlen(*ptr):0; diff --git a/engine/common/zone.h b/engine/common/zone.h index 25decbd1c..ea8df49b6 100644 --- a/engine/common/zone.h +++ b/engine/common/zone.h @@ -151,6 +151,7 @@ void QDECL ZG_FreeGroup(zonegroup_t *ctx); char *Z_StrDupf(const char *format, ...); void Z_StrCat(char **ptr, const char *append); +void Z_StrCatLen(char **ptr, const char *append, size_t newlen); //still doesn't allow nulls, but src doesn't need null termination. /* void *Hunk_Alloc (int size); // returns 0 filled memory diff --git a/engine/http/httpserver.c b/engine/http/httpserver.c index 42e29ccd9..74c7df054 100644 --- a/engine/http/httpserver.c +++ b/engine/http/httpserver.c @@ -305,7 +305,7 @@ const char *HTTP_RunClient (HTTP_active_connections_t *cl) char *content; char *msg, *nl; char buf2[2560]; //short lived temp buffer. - char resource[2560]; + char resource[2560], *args; char host[256]; char mode[80]; qboolean hostspecified; @@ -389,6 +389,10 @@ const char *HTTP_RunClient (HTTP_active_connections_t *cl) } } + args = strchr(resource, '?'); + if (args) + *args++=0; + if (!strcmp(resource, "/")) strcpy(resource, "/index.html"); diff --git a/engine/server/pr_cmds.c b/engine/server/pr_cmds.c index 0d6d5d094..c9baafec6 100644 --- a/engine/server/pr_cmds.c +++ b/engine/server/pr_cmds.c @@ -6462,18 +6462,31 @@ char *PF_infokey_Internal (int entnum, const char *key) sprintf(ov, "%d", SV_CalcPing (&svs.clients[entnum-1], true)); else if (!strcmp(key, "guid")) sprintf(ov, "%s", pl->guid); - else if (!strcmp(key, "*cert_sha1")) - { - char buf[8192]; - char digest[DIGEST_MAXSIZE]; - int certsize = NET_GetConnectionCertificate(svs.sockets, &controller->netchan.remote_address, QCERT_PEERCERTIFICATE, buf, sizeof(buf)); - if (certsize <= 0) - value = ""; - else - Base64_EncodeBlockURI(digest,CalcHash(&hash_sha1, digest, sizeof(digest), buf, certsize), ov, sizeof(ov)); - } else if (!strcmp(key, "*cert_dn")) NET_GetConnectionCertificate(svs.sockets, &controller->netchan.remote_address, QCERT_PEERSUBJECT, ov, sizeof(ov)); + else if (!strncmp(key, "*cert_", 6)) + { + static struct + { + const char *name; + hashfunc_t *func; + } funcs[] = {{"sha1",&hash_sha1}, {"sha2_256", &hash_sha2_256}, {"sha2_512", &hash_sha2_512}}; + int i; + char buf[8192]; + char digest[DIGEST_MAXSIZE]; + int certsize; + *ov = 0; + for (i = 0; i < countof(funcs); i++) + { + if (!strcmp(key+6, funcs[i].name)) + { + certsize = NET_GetConnectionCertificate(svs.sockets, &controller->netchan.remote_address, QCERT_PEERCERTIFICATE, buf, sizeof(buf)); + if (certsize > 0) + Base64_EncodeBlockURI(digest,CalcHash(&hash_sha1, digest, sizeof(digest), buf, certsize), ov, sizeof(ov)); + break; + } + } + } else if (!strcmp(key, "challenge")) sprintf(ov, "%u", pl->challenge); else if (!strcmp(key, "*userid")) diff --git a/engine/server/server.h b/engine/server/server.h index 6c930f069..2cb1d0ade 100644 --- a/engine/server/server.h +++ b/engine/server/server.h @@ -697,6 +697,7 @@ typedef struct client_s qboolean qex; //qex sends strange clc inputs and needs workarounds for its prediction. it also favours fitzquake's protocol but violates parts of it. unsigned int lastruncmd; //for non-qw physics. timestamp they were last run, so switching between physics modes isn't a (significant) cheat + unsigned int hoverms; //purely for sv_showpredloss to avoid excessive spam //speed cheat testing #define NEWSPEEDCHEATPROT float msecs; @@ -1385,10 +1386,12 @@ void SV_SendClientPrespawnInfo(client_t *client); void SV_ClientProtocolExtensionsChanged(client_t *client); //sv_master.c -float SVM_Think(int port); +float SVM_Think(void); vfsfile_t *SVM_GenerateIndex(const char *requesthost, const char *fname, const char **mimetype, const char *query); void SVM_AddBrokerGame(const char *brokerid, const char *info); void SVM_RemoveBrokerGame(const char *brokerid); +qboolean SVM_FixupServerAddress(netadr_t *adr, struct dtlspeercred_s *cred); +void FTENET_TCP_ICEResponse(struct ftenet_connections_s *col, int type, const char *cid, const char *sdp); // diff --git a/engine/server/sv_ccmds.c b/engine/server/sv_ccmds.c index a85fc3a4b..974da9888 100644 --- a/engine/server/sv_ccmds.c +++ b/engine/server/sv_ccmds.c @@ -2160,9 +2160,6 @@ static void SV_Status_f (void) #ifdef QWOVERQ3 extern cvar_t sv_listen_q3; #endif -#ifdef HAVE_DTLS - extern cvar_t net_enable_dtls; -#endif #ifndef SERVERONLY if (!sv.state && cls.state >= ca_connected && !cls.demoplayback && cls.protocol == CP_NETQUAKE) @@ -2210,9 +2207,6 @@ static void SV_Status_f (void) s = "private"; Con_TPrintf("public : %s\n", s); -#ifdef HAVE_DTLS - Con_TPrintf("fingerprint : "S_COLOR_GRAY"%s\n", InfoBuf_ValueForKey(&svs.info, "*fp")); -#endif switch(svs.gametype) { #ifdef Q3SERVER diff --git a/engine/server/sv_main.c b/engine/server/sv_main.c index c4fbd92c4..9351ece52 100644 --- a/engine/server/sv_main.c +++ b/engine/server/sv_main.c @@ -140,7 +140,6 @@ cvar_t sv_listen_q3 = CVAR("sv_listen_q3", "0"); #endif cvar_t sv_reconnectlimit = CVARD("sv_reconnectlimit", "0", "Blocks dupe connection within the specified length of time ."); cvar_t sv_use_dns = CVARD("sv_use_dns", "", "Performs a reverse-dns lookup in order to report more info about where clients are connecting from."); -extern cvar_t net_enable_dtls; cvar_t sv_reportheartbeats = CVARD("sv_reportheartbeats", "2", "Print a notice each time a heartbeat is sent to a master server. When set to 2, the message will be displayed once."); cvar_t sv_heartbeat_interval = CVARD("sv_heartbeat_interval", "110", "Interval between heartbeats. Low values are abusive, high values may cause NAT/ghost issues."); cvar_t sv_heartbeat_checks = CVARD("sv_heartbeat_checks", "1", "Report when sv_public 1 fails due to PROBABLE router/NAT issues."); @@ -162,7 +161,6 @@ cvar_t sv_pupglow = CVARFD("sv_pupglow", "", CVAR_SERVERINFO, "Instructs clie #ifdef SV_MASTER cvar_t sv_master = CVAR("sv_master", "0"); -cvar_t sv_masterport = CVAR("sv_masterport", "0"); #endif cvar_t sv_reliable_sound = CVARFD("sv_reliable_sound", "0", 0, "Causes all sounds to be sent reliably, so they will not be missed due to packetloss. However, this will cause them to be delayed somewhat, and slightly bursty. This can be overriden using the 'rsnd' userinfo setting (either forced on or forced off). Note: this does not affect sounds attached to particle effects."); @@ -3113,9 +3111,12 @@ void SV_DoDirectConnect(svconnectinfo_t *fte_restrict info) newcl->netchan.mtu = info->mtu; newcl->netchan.message.maxsize = sizeof(newcl->netchan.message_buf); +#ifdef HAVE_ICE if (info->adr.type == NA_ICE) newcl->netchan.mtu -= 48+12; //dtls+sctp overhead - else if (info->adr.prot == NP_DTLS || info->adr.prot == NP_TLS) + else +#endif + if (info->adr.prot == NP_DTLS || info->adr.prot == NP_TLS) newcl->netchan.mtu -= 48; //dtls overhead } else @@ -4042,9 +4043,14 @@ void SVC_ACK (void) } } } - Con_TPrintf ("A2A_ACK from %s\n", NET_AdrToString (adr, sizeof(adr), &net_from)); + Con_TPrintf (S_COLOR_GRAY"A2A_ACK from %s\n", NET_AdrToString (adr, sizeof(adr), &net_from)); } +#ifdef SUPPORT_ICE +void SVC_ICE_Offer(void); +void SVC_ICE_Candidate(void); +#endif + //returns false to block replies //this is to mitigate wasted bandwidth if we're used as a udp amplification qboolean SVC_ThrottleInfo (void) @@ -4083,7 +4089,7 @@ static struct attacker_s } *dosattacker; static size_t dosattacker_count; static size_t dosattacker_max; -#define dosattacker_limit 10 //if we get X packets +#define dosattacker_limit 15 //if we get X packets #define dosattacker_period 30 //within Y secs #define dosattacker_blocktime (60*60*24) //block them for Z secs (24 hours). static qboolean SV_DetectAmplificationDDOS (void) @@ -4286,8 +4292,10 @@ qboolean SV_ConnectionlessPacket (void) else { //NET_DTLS_Disconnect(svs.sockets, &net_from); - if (NET_DTLS_Create(svs.sockets, &net_from, NULL)) + if (NET_DTLS_Create(svs.sockets, &net_from, NULL, false)) Netchan_OutOfBandPrint(NS_SERVER, &net_from, "dtlsopened"); + else + SV_RejectMessage (SCP_QUAKEWORLD, "DTLS driver failure.\n"); } } else @@ -4332,6 +4340,13 @@ qboolean SV_ConnectionlessPacket (void) } else if (!strcmp(c, "realip") || !strcmp(c, "ip")) SVC_RealIP (); + +#ifdef SUPPORT_ICE + else if (!strcmp(c, "ice_offer")) + SVC_ICE_Offer(); + else if (!strcmp(c, "ice_ccand")) + SVC_ICE_Candidate(); +#endif /* else if (!strcmp(c,"lastscores")) { @@ -5077,10 +5092,6 @@ qboolean SV_ReadPackets (float *delay) NET_ReadPackets(svs.sockets); -#ifdef HAVE_DTLS - NET_DTLS_Timeouts(svs.sockets); -#endif - if (inboundsequence == oldinboundsequence) return false; //nothing new. oldinboundsequence = inboundsequence; @@ -5513,12 +5524,7 @@ float SV_Frame (void) #ifdef SV_MASTER if (sv_master.ival) - { - if (sv_masterport.ival) - SVM_Think(sv_masterport.ival); - else - SVM_Think(PORT_QWMASTER); - } + SVM_Think(); #endif #ifdef PLUGINS @@ -5882,7 +5888,6 @@ void SV_InitLocal (void) Cvar_Register (&sv_banproxies, cvargroup_serverpermissions); #ifdef SV_MASTER Cvar_Register (&sv_master, cvargroup_servercontrol); - Cvar_Register (&sv_masterport, cvargroup_servercontrol); #endif Cvar_Register (&filterban, cvargroup_servercontrol); diff --git a/engine/server/sv_master.c b/engine/server/sv_master.c index 770ac7416..0ca1318ea 100644 --- a/engine/server/sv_master.c +++ b/engine/server/sv_master.c @@ -45,6 +45,7 @@ typedef struct svm_server_s { unsigned int bots; //non-human players unsigned int clients; //human players unsigned int maxclients; //limit of bots+clients, but not necessarily spectators. + int secure:1; int needpass:1; int coop:1; char hostname[64]; //just for our own listings. @@ -189,6 +190,28 @@ static svm_server_t *SVM_GetServer(netadr_t *adr) } return NULL; } +qboolean SVM_FixupServerAddress(netadr_t *adr, struct dtlspeercred_s *cred) +{ //if we get a request to send an ice offer over udp, make sure we respond from the socket they heartbeated from, so their (possible) nat won't block us. + //also make sure the fingerprint stuff is okay. + svm_server_t *sv = SVM_GetServer(adr); + char *fp; + size_t b; + if (!sv) + return false; + *adr = sv->adr; //fix it up (mostly the connum so it follows the proper 'return' route) + fp = Info_ValueForKey(sv->rules, "*fp"); + b = Base64_DecodeBlock(fp, NULL, cred->digest, sizeof(cred->digest)); + if (b <= 20) + cred->hash = &hash_sha1; + else if (b <= 256/8) + cred->hash = &hash_sha2_256; + else if (b <= 512/8) + cred->hash = &hash_sha2_512; + else + return false; //just no. + memset(cred->digest+b, 0, cred->hash->digestsize-b); //make sure its -terminated, in case the provided size was wrong + return true; +} static svm_game_t *SVM_FindGame(const char *game, int create) { @@ -579,7 +602,7 @@ vfsfile_t *SVM_Generate_Gamelist(const char **mimetype, const char *query) if (game->numservers || !sv_hideinactivegames.ival) //only show active servers { QuakeCharsToHTML(tmpbuf, sizeof(tmpbuf), game->name, true); - VFS_PRINTF(f, "%s%u player%s", game->name, query?"?":"", query?query:"", tmpbuf, clients, clients==1?"":"s"); + VFS_PRINTF(f, "%s%u player%s", game->name, query?"?":"", query?query:"", tmpbuf, clients, clients==1?"":"s"); if (bots) VFS_PRINTF(f, ", %u bot%s", bots, bots==1?"":"s"); if (specs) @@ -634,12 +657,12 @@ static int QDECL SVM_SortServerRule(const void *r1, const void *r2) vfsfile_t *SVM_Generate_Serverinfo(const char **mimetype, const char *serveraddr, const char *query) { vfsfile_t *f = VFSPIPE_Open(1, false); - char tmpbuf[256]; + char tmpbuf[512]; char hostname[1024]; svm_server_t *server; netadr_t adr[64]; size_t count, u; - const char *url; + const char *url, *fp; VFS_PRINTF(f, "%s", master_css); VFS_PRINTF(f, "

Single Server Info

\n"); @@ -656,10 +679,17 @@ vfsfile_t *SVM_Generate_Serverinfo(const char **mimetype, const char *serveraddr QuakeCharsToHTML(hostname, sizeof(hostname), server->hostname, false); url = NET_AdrToString(tmpbuf, sizeof(tmpbuf), &server->adr); + fp = Info_ValueForKey(server->rules, "*fp"); + if (*fp) + fp = va("?fp=%s", Info_ValueForKey(server->rules, "*fp")); if (server->game->scheme && !server->brokerid) - url = va("%s", server->game->scheme, url, url); + url = va("%s", server->game->scheme, url,fp, url); - VFS_PRINTF(f, "%s%s%s%s%s%s%u/%u\n", server->game?server->game->name:"Unknown", url, (server->needpass&1)?"🔒":"", hostname, server->gamedir, server->mapname, server->clients, server->maxclients); + VFS_PRINTF(f, "%s%s%s%s%s%s%s%s%u/%u\n", + server->game?server->game->name:"Unknown", query?"?":"", query?query:"", server->game?server->game->name:"Unknown", //game column + url, //address column + server->secure?"🛡":"🚫", (server->needpass&1)?"🔒":"", (server->coop&1)?"🚸":"", hostname, //hostname column + server->gamedir, server->mapname, server->clients, server->maxclients); VFS_PRINTF(f, "\n"); VFS_PRINTF(f, "
\n"); @@ -749,7 +779,7 @@ vfsfile_t *SVM_Generate_Serverlist(const char **mimetype, const char *masteraddr infourl = url = NET_AdrToString(tmpbuf, sizeof(tmpbuf), &server->adr); } QuakeCharsToHTML(hostname, sizeof(hostname), server->hostname, false); - VFS_PRINTF(f, "%s%s%s%s%s%s%u", infourl, url, (server->needpass&1)?"🔒":"", (server->coop&1)?"🚸":"", hostname, server->gamedir, server->mapname, server->clients); + VFS_PRINTF(f, "%s%s%s%s%s%s%s%u", infourl, url, server->secure?"🛡":"🚫", (server->needpass&1)?"🔒":"", (server->coop&1)?"🚸":"", hostname, server->gamedir, server->mapname, server->clients); if (server->bots) VFS_PRINTF(f, "+%ub", server->bots); VFS_PRINTF(f, "/%u", server->maxclients); @@ -786,6 +816,10 @@ vfsfile_t *SVM_Generate_Rawlist(const char **mimetype, const char *masteraddr, c svm_game_t *game; svm_server_t *server; vfsfile_t *f = VFSPIPE_Open(1, false); + char *fp; + char *prot; + + masteraddr = ""; //client should work this out based on where it got the list from. COM_StripExtension(gamename, tmpbuf, sizeof(tmpbuf)); game = SVM_FindGame(tmpbuf, false); @@ -794,8 +828,13 @@ vfsfile_t *SVM_Generate_Rawlist(const char **mimetype, const char *masteraddr, c VFS_PRINTF(f, "#Server list for \"%s\"\n", tmpbuf); for (server = (game?game->firstserver:NULL); server; server = server->next) { + prot = Info_ValueForKey(server->rules, "protocol"); + if (!*prot) + prot = va("%i", server->protover); if (server->brokerid) - VFS_PRINTF(f, "rtc://%s/%s \\maxclients\\%u\\clients\\%u\\bots\\%u\\hostname\\%s\\modname\\%s\\mapname\\%s\\needpass\\%i\n", masteraddr, server->brokerid, server->maxclients, server->clients, server->bots, *server->hostname?server->hostname:"unnamed", *server->gamedir?server->gamedir:"-", *server->mapname?server->mapname:"-", server->needpass); + VFS_PRINTF(f, "rtc://%s/%s \\protocol\\%s\\maxclients\\%u\\clients\\%u\\bots\\%u\\hostname\\%s\\modname\\%s\\mapname\\%s\\needpass\\%i\n", masteraddr, server->brokerid, prot, server->maxclients, server->clients, server->bots, *server->hostname?server->hostname:"unnamed", *server->gamedir?server->gamedir:"-", *server->mapname?server->mapname:"-", server->needpass?1:0); + else if ((fp = Info_ValueForKey(server->rules, "*fp"))) + VFS_PRINTF(f, "rtc://%s/udp/%s \\protocol\\%s\\maxclients\\%u\\clients\\%u\\bots\\%u\\hostname\\%s\\modname\\%s\\mapname\\%s\\needpass\\%i\\*fp\\%s\n", masteraddr, NET_AdrToString(tmpbuf, sizeof(tmpbuf), &server->adr), prot, server->maxclients, server->clients, server->bots, *server->hostname?server->hostname:"unnamed", *server->gamedir?server->gamedir:"-", *server->mapname?server->mapname:"-", server->needpass?1:0, fp); else VFS_PRINTF(f, "%s\n", NET_AdrToString(tmpbuf, sizeof(tmpbuf), &server->adr)); } @@ -1112,6 +1151,13 @@ static void SVM_ProcessUDPPacket(void) *(int*)&net_from.address.ip6[12]=0; } +#ifdef HAVE_DTLS + if (*(int *)net_message.data != -1) + if (NET_DTLS_Decode(svm_sockets)) + if (!net_message.cursize) + return; +#endif + if (NET_WasSpecialPacket(svm_sockets)) { svm.total.stun++; @@ -1258,7 +1304,6 @@ static void SVM_ProcessUDPPacket(void) SVM_GenChallenge(ourchallenge, sizeof(ourchallenge), &net_from); if (!strcmp(chal, ourchallenge)) { - bots = atoi(Info_ValueForKey(s, "bots")); clients = atoi(Info_ValueForKey(s, "clients")); clients = max(0, clients-bots); @@ -1272,11 +1317,13 @@ static void SVM_ProcessUDPPacket(void) if (srv) { Q_strncpyz(srv->rules, s, sizeof(srv->rules)); + Info_RemoveKey(srv->rules, "challenge"); //prevent poisoning if (developer.ival) Info_Print(s, "\t"); if (game) srv->protover = atoi(Info_ValueForKey(s, "protocol")); srv->maxclients = atoi(Info_ValueForKey(s, "sv_maxclients")); + srv->secure = !!*Info_ValueForKey(s, "*fp"); srv->needpass = atoi(Info_ValueForKey(s, "needpass")); srv->coop = atoi(Info_ValueForKey(s, "coop")); if (!srv->coop) @@ -1453,6 +1500,7 @@ static void SVM_ProcessUDPPacket(void) Info_Print(s, "\t"); srv->protover = 3;//atoi(Info_ValueForKey(s, "protocol")); srv->maxclients = atoi(Info_ValueForKey(s, "maxclients")); + srv->secure = !!*Info_ValueForKey(s, "*fp"); srv->needpass = atoi(Info_ValueForKey(s, "needpass")); srv->coop = atoi(Info_ValueForKey(s, "coop")); if (!srv->coop) @@ -1500,6 +1548,14 @@ static void SVM_ProcessUDPPacket(void) //this isn't actually useful. we can't use it because we can't protect against spoofed denial-of-service attacks. //we could only use this by sending it a few pings to see if it is actually still responding. which is unreliable (especially if we're getting spammed by packet floods). } + else if (!strcmp(com_token, "ice_answer")) + { //one of our ws clients sent an ice offer over udp. this is the reply... hopefully. + FTENET_TCP_ICEResponse(svm_sockets, ICEMSG_OFFER, s, MSG_ReadString()); + } + else if (!strcmp(com_token, "ice_scand")) + { //a send or ack... + FTENET_TCP_ICEResponse(svm_sockets, ICEMSG_CANDIDATE, s, MSG_ReadString()); + } else svm.total.junk++; } @@ -1577,14 +1633,123 @@ float SVM_RequerySlaves(void) return 4; //nothing happening. } -float SVM_Think(int port) + +static void SVM_RegisterAlias(svm_game_t *game, char *aliasname) { + const char *a; + size_t l; + svm_game_t *aliasgame; + if (!game) + return; + + //make sure we never have dupes. they confuse EVERYTHING. + aliasgame = SVM_FindGame(aliasname, false); + if (aliasgame == game) + return; //already in there somehow. + if (aliasgame) + { + Con_Printf("game alias of %s is already registered\n", aliasname); + return; + } + game->persistent = true; //don't forget us! + + if (!*aliasname) + return; + + a = game->aliases; + if (a) for (; *a; a+=strlen(a)+1); + l = a-game->aliases; + game->aliases = BZ_Realloc(game->aliases, l+strlen(aliasname)+2); + memcpy(game->aliases+l, aliasname, strlen(aliasname)+1); + l += strlen(aliasname)+1; + game->aliases[l] = 0; +} +static void SVM_GameAlias_f(void) +{ + svm_game_t *game = SVM_FindGame(Cmd_Argv(1), 2); + if (!game) + { + Con_Printf("Unable to register game %s\n", Cmd_Argv(1)); + return; + } + SVM_RegisterAlias(game, Cmd_Argv(2)); +} +static void SVM_Register(void) +{ + size_t u; + + svm_sockets = FTENET_CreateCollection(true, SVM_ProcessUDPPacket); + Hash_InitTable(&svm.serverhash, 1024, Z_Malloc(Hash_BytesForBuckets(1024))); + + Cmd_AddCommand ("gamealias", SVM_GameAlias_f); + + Cvar_Register(&sv_masterport, "server control variables"); + Cvar_Register(&sv_masterport_tcp, "server control variables"); + Cvar_Register(&sv_heartbeattimeout, "server control variables"); + Cvar_Register(&sv_maxgames, "server control variables"); + Cvar_Register(&sv_maxservers, "server control variables"); + Cvar_Register(&sv_hideinactivegames, "server control variables"); + Cvar_Register(&sv_sortlist, "server control variables"); + Cvar_Register(&sv_hostname, "server control variables"); + Cvar_Register(&sv_slaverequery, "server control variables"); + for (u = 0; u < countof(sv_masterslave); u++) + Cvar_Register(&sv_masterslave[u].var, "server control variables"); +} +static qboolean SVM_FoundManifest(void *usr, ftemanifest_t *man) +{ + svm_game_t *game; + const char *g; + if (man->protocolname) + { //FIXME: we ought to do this for each manifest we could find. + g = man->protocolname; + +#if 1 + game = SVM_FindGame(man->formalname, 2); +#else + g = COM_Parse(g); + game = SVM_FindGame(com_token, 2); +#endif + if (!game) + return false; + if (man->schemes && !game->scheme) + { + COM_Parse(man->schemes); + game->scheme = Z_StrDup(com_token); + } + while (*g) + { + g = COM_Parse(g); + SVM_RegisterAlias(game, com_token); + } + } + + return false; +} +static void SVM_Begin(void) +{ //called once filesystem etc stuff is started. + SVM_FoundManifest(NULL, fs_manifest); + FS_EnumerateKnownGames(SVM_FoundManifest, NULL); + + Cvar_ForceCallback(&sv_masterport); + Cvar_ForceCallback(&sv_masterport_tcp); +} + +float SVM_Think(void) +{ +#ifndef MASTERONLY + if (!svm_sockets) + { + SVM_Register(); + SVM_Begin(); + } +#endif + NET_ReadPackets (svm_sockets); SVM_RemoveOldServers(); return SVM_RequerySlaves(); } #else -float SVM_Think(int port){return 4;} +float SVM_Think(void){return 4;} #endif @@ -1631,81 +1796,9 @@ static void SVM_Status_f(void) } -static void SVM_RegisterAlias(svm_game_t *game, char *aliasname) -{ - const char *a; - size_t l; - svm_game_t *aliasgame; - if (!game) - return; - - //make sure we never have dupes. they confuse EVERYTHING. - aliasgame = SVM_FindGame(aliasname, false); - if (aliasgame == game) - return; //already in there somehow. - if (aliasgame) - { - Con_Printf("game alias of %s is already registered\n", aliasname); - return; - } - game->persistent = true; //don't forget us! - - if (!*aliasname) - return; - - a = game->aliases; - if (a) for (; *a; a+=strlen(a)+1); - l = a-game->aliases; - game->aliases = BZ_Realloc(game->aliases, l+strlen(aliasname)+2); - memcpy(game->aliases+l, aliasname, strlen(aliasname)+1); - l += strlen(aliasname)+1; - game->aliases[l] = 0; -} -static qboolean SVM_FoundManifest(void *usr, ftemanifest_t *man) -{ - svm_game_t *game; - const char *g; - if (man->protocolname) - { //FIXME: we ought to do this for each manifest we could find. - g = man->protocolname; - -#if 1 - game = SVM_FindGame(man->formalname, 2); -#else - g = COM_Parse(g); - game = SVM_FindGame(com_token, 2); -#endif - if (!game) - return false; - if (man->schemes && !game->scheme) - { - COM_Parse(man->schemes); - game->scheme = Z_StrDup(com_token); - } - while (*g) - { - g = COM_Parse(g); - SVM_RegisterAlias(game, com_token); - } - } - - return false; -} - -static void SVM_GameAlias_f(void) -{ - svm_game_t *game = SVM_FindGame(Cmd_Argv(1), 2); - if (!game) - { - Con_Printf("Unable to register game %s\n", Cmd_Argv(1)); - return; - } - SVM_RegisterAlias(game, Cmd_Argv(2)); -} void SV_Init (struct quakeparms_s *parms) { int manarg; - size_t u; COM_InitArgv (parms->argc, parms->argv); @@ -1731,22 +1824,8 @@ void SV_Init (struct quakeparms_s *parms) Cmd_AddCommand ("quit", SV_Quit_f); Cmd_AddCommand ("status", SVM_Status_f); - Cmd_AddCommand ("gamealias", SVM_GameAlias_f); - svm_sockets = FTENET_CreateCollection(true, SVM_ProcessUDPPacket); - Hash_InitTable(&svm.serverhash, 1024, Z_Malloc(Hash_BytesForBuckets(1024))); - - Cvar_Register(&sv_masterport, "server control variables"); - Cvar_Register(&sv_masterport_tcp, "server control variables"); - Cvar_Register(&sv_heartbeattimeout, "server control variables"); - Cvar_Register(&sv_maxgames, "server control variables"); - Cvar_Register(&sv_maxservers, "server control variables"); - Cvar_Register(&sv_hideinactivegames, "server control variables"); - Cvar_Register(&sv_sortlist, "server control variables"); - Cvar_Register(&sv_hostname, "server control variables"); - Cvar_Register(&sv_slaverequery, "server control variables"); - for (u = 0; u < countof(sv_masterslave); u++) - Cvar_Register(&sv_masterslave[u].var, "server control variables"); + SVM_Register(); Cvar_ParseWatches(); host_initialized = true; @@ -1760,11 +1839,7 @@ void SV_Init (struct quakeparms_s *parms) Cmd_StuffCmds(); Cbuf_Execute (); - Cvar_ForceCallback(&sv_masterport); - Cvar_ForceCallback(&sv_masterport_tcp); - - SVM_FoundManifest(NULL, fs_manifest); - FS_EnumerateKnownGames(SVM_FoundManifest, NULL); + SVM_Begin(); Con_Printf ("Exe: %s\n", version_string()); @@ -1785,7 +1860,7 @@ float SV_Frame (void) } Cbuf_Execute (); - sleeptime = SVM_Think(sv_masterport.ival); + sleeptime = SVM_Think(); //record lots of info over multiple frames, for smoother stats info. svm.total.timestamp = realtime; diff --git a/engine/server/sv_mvd.c b/engine/server/sv_mvd.c index 0d8e7bc3d..0e5519442 100644 --- a/engine/server/sv_mvd.c +++ b/engine/server/sv_mvd.c @@ -468,7 +468,7 @@ int SV_MVD_GotQTVRequest(vfsfile_t *clientstream, char *headerstart, char *heade { char tmp[32]; Sys_RandomBytes(tmp, sizeof(tmp)); - tobase64(p->challenge, sizeof(p->challenge), tmp, sizeof(tmp)); + Base64_EncodeBlock(tmp, sizeof(tmp), p->challenge, sizeof(p->challenge)); } e = va("QTVSV 1\n" diff --git a/engine/server/sv_send.c b/engine/server/sv_send.c index 7d5897dd0..aa174381d 100644 --- a/engine/server/sv_send.c +++ b/engine/server/sv_send.c @@ -3499,8 +3499,7 @@ void SV_SendClientMessages (void) stepmsec = 13; cmd.msec = stepmsec; - if (sv_showpredloss.ival) - Con_Printf("%s: forcing %g msecs (anti-hover)\n", c->name, cmd.msec); + c->hoverms += cmd.msec; VectorCopy(c->lastcmd.angles, cmd.angles); cmd.buttons = c->lastcmd.buttons; SV_RunCmd (&cmd, true); diff --git a/engine/server/sv_user.c b/engine/server/sv_user.c index 183b13f3b..2b4930ddf 100644 --- a/engine/server/sv_user.c +++ b/engine/server/sv_user.c @@ -7240,6 +7240,12 @@ void SV_RunCmd (usercmd_t *ucmd, qboolean recurse) #ifdef NEWSPEEDCHEATPROT if (ucmd->msec && host_client->msecs > 500) host_client->msecs = 500; + if (host_client->hoverms) + { + if (sv_showpredloss.ival) + Con_Printf("%s: forcing %g msecs (anti-hover)\n", host_client->name, cmd.msec); + host_client->hoverms = 0; + } if (ucmd->msec > host_client->msecs) { //they're over their timeslice allocation //if they're not taking the piss then be prepared to truncate the frame. this should hide clockskew without allowing full-on speedcheats. diff --git a/engine/web/ftejslib.js b/engine/web/ftejslib.js index 05afc6b56..05d288812 100644 --- a/engine/web/ftejslib.js +++ b/engine/web/ftejslib.js @@ -794,6 +794,8 @@ mergeInto(LibraryManager.library, return -1; if (s.con == 0) return 0; //not connected yet + if (s.err != 0) + return -1; if (len == 0) return 0; //... s.ws.send(HEAPU8.subarray(data, data+len)); @@ -888,6 +890,29 @@ mergeInto(LibraryManager.library, s.recvchan.binaryType = 'arraybuffer'; s.recvchan.onmessage = s.ws.onmessage; }; + s.pc.onconnectionstatechange = function(e) + { +//console.log(s.pc.connectionState); +//console.log(e); + switch (s.pc.connectionState) + { + //case "new": + //case "checking": + //case "connected": + case "disconnected": + s.err = 1; + break; + case "closed": + s.con = 0; + s.err = 1; + break; + case "failed": + s.err = 1; + break; + default: + break; + } + }; if (clientside) { diff --git a/engine/web/sys_web.c b/engine/web/sys_web.c index fdc2c9bd0..8273214ce 100644 --- a/engine/web/sys_web.c +++ b/engine/web/sys_web.c @@ -49,12 +49,50 @@ qboolean Sys_RandomBytes(qbyte *string, int len) void Sys_Printf (char *fmt, ...) { va_list argptr; - char buf[1024]; + char text[2048]; + conchar_t ctext[2048], *e, *c; + unsigned int len = 0; + unsigned int w, codeflags; va_start (argptr,fmt); - vsnprintf (buf, sizeof(buf), fmt, argptr); - emscriptenfte_print(buf); + vsnprintf (text, sizeof(text), fmt, argptr); va_end (argptr); + + //make sense of any markup + e = COM_ParseFunString(CON_WHITEMASK, text, ctext, sizeof(ctext), false); + + //convert to utf-8 for the js to make sense of + for (c = ctext; c < e; ) + { + c = Font_Decode(c, &codeflags, &w); + if (codeflags & CON_HIDDEN) + continue; + + //dequake it as required, so its only codepoints the browser will understand. should probably deal with linefeeds specially. + if (w >= 0xe000 && w < 0xe100) + { //quake-encoded mess + if ((w & 0x7f) >= 0x20) + w &= 0x7f; //regular (discoloured) ascii + else if (w & 0x80) + { //c1 glyphs + static char tab[32] = "---#@.@@@@ # >.." "[]0123456789.---"; + w = tab[w&31]; + } + else + { //c0 glyphs + static char tab[32] = ".####.#### # >.." "[]0123456789.---"; + w = tab[w&31]; + } + } + else if (w < ' ' && w != '\t' && w != '\r' && w != '\n') + w = '?'; //c0 chars are awkward + + len += utf8_encode(text+len, w, sizeof(text)-1-len); + } + text[len] = 0; + + //now throw it at the browser's console.log. + emscriptenfte_print(text); } #if 1