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).

This commit is contained in:
Shpoike 2023-03-15 17:57:31 +00:00
parent 92be91f416
commit 527233154c
26 changed files with 1598 additions and 570 deletions

View file

@ -1026,6 +1026,7 @@ ELSE()
engine/common/cvar.c engine/common/cvar.c
engine/common/cmd.c engine/common/cmd.c
engine/common/sha1.c #for websockets engine/common/sha1.c #for websockets
engine/common/sha2.c #for fingerprints
engine/http/httpclient.c #for the pipe stuff engine/http/httpclient.c #for the pipe stuff
engine/common/log.c engine/common/log.c
engine/common/fs.c engine/common/fs.c

View file

@ -187,9 +187,6 @@ cvar_t cl_gunanglex = CVAR("cl_gunanglex", "0");
cvar_t cl_gunangley = CVAR("cl_gunangley", "0"); cvar_t cl_gunangley = CVAR("cl_gunangley", "0");
cvar_t cl_gunanglez = CVAR("cl_gunanglez", "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_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_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."); 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; connectinfo.numadr = 0;
SCR_EndLoadingPlaque(); SCR_EndLoadingPlaque();
connectinfo.trying = false; 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 (); t1 = Sys_DoubleTime ();
#ifdef HAVE_DTLS
if (connectinfo.peercred.hash && net_enable_dtls.ival>0) if (connectinfo.peercred.hash && net_enable_dtls.ival>0)
{ {
char cert[8192]; char cert[8192];
@ -693,6 +703,7 @@ static void CL_SendConnectPacket (netadr_t *to)
return; return;
} }
} }
#endif
if (!to) if (!to)
{ {
@ -844,7 +855,7 @@ static void CL_ResolvedServer(void *vctx, void *data, size_t a, size_t b)
if (!ctx->found) if (!ctx->found)
{ {
CL_ConnectAbort("Bad server address \"%s\"\n", ctx->servername); CL_ConnectAbort("Unable to resolve server address \"%s\"\n", ctx->servername);
return; return;
} }
@ -1293,7 +1304,7 @@ void CL_CheckForResend (void)
connectinfo.clogged = false; connectinfo.clogged = false;
if (connectinfo.tries == 0 && connectinfo.nextadr < connectinfo.numadr) 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); CL_ConnectAbort ("Unable to establish connection to %s\n", cls.servername);
return; return;
@ -1499,8 +1510,15 @@ static void CL_BeginServerConnect(char *host, int port, qboolean noproxy, enum c
*e=0; *e=0;
if (!strncasecmp(arglist, "fp=", 3)) if (!strncasecmp(arglist, "fp=", 3))
{ {
Base64_DecodeBlock(arglist+3, arglist+strlen(arglist), connectinfo.peercred.digest, sizeof(connectinfo.peercred.digest)); size_t l = 8*Base64_DecodeBlock(arglist+3, arglist+strlen(arglist), connectinfo.peercred.digest, sizeof(connectinfo.peercred.digest));
connectinfo.peercred.hash = &hash_sha1; 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 else
Con_Printf(CON_WARNING"uri arg not known: \"%s\"\n", arglist); Con_Printf(CON_WARNING"uri arg not known: \"%s\"\n", arglist);
@ -3110,7 +3128,7 @@ void CL_Packet_f (void)
if (!cls.sockets) if (!cls.sockets)
NET_InitClient(false); NET_InitClient(false);
if (!NET_EnsureRoute(cls.sockets, "packet", &cred, &adr)) if (!NET_EnsureRoute(cls.sockets, "packet", &cred, &adr, true))
return; return;
NET_SendPacket (cls.sockets, out-send, send, &adr); NET_SendPacket (cls.sockets, out-send, send, &adr);
@ -3432,7 +3450,7 @@ void CL_ConnectionlessPacket (void)
if (CL_IsPendingServerAddress(&net_from)) if (CL_IsPendingServerAddress(&net_from))
{ {
struct dtlspeercred_s cred = {cls.servername}; //FIXME 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); Con_Printf (CON_ERROR"Unable to redirect to %s\n", data);
else else
{ {
@ -3896,7 +3914,7 @@ void CL_ConnectionlessPacket (void)
memset(&cred, 0, sizeof(cred)); memset(&cred, 0, sizeof(cred));
cred.peer = connectinfo.peercred; 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.numadr = 1; //fixate on this resolved address.
connectinfo.adr[0] = net_from; connectinfo.adr[0] = net_from;
@ -3942,15 +3960,6 @@ client_connect: //fixme: make function
Con_TPrintf ("ignoring connection\n"); Con_TPrintf ("ignoring connection\n");
return; 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) if (cls.state >= ca_connected)
{ {
@ -3968,6 +3977,15 @@ client_connect: //fixme: make function
return; 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; connectinfo.trying = false;
cl.splitclients = 0; cl.splitclients = 0;
cls.protocol = connectinfo.protocol; cls.protocol = connectinfo.protocol;
@ -4383,10 +4401,6 @@ void CL_ReadPackets (void)
else else
NET_ReadPackets(cls.sockets); NET_ReadPackets(cls.sockets);
#ifdef HAVE_DTLS
NET_DTLS_Timeouts(cls.sockets);
#endif
// //
// check timeout // check timeout
// //

View file

@ -1499,6 +1499,7 @@ static int CL_LoadModels(int stage, qboolean dontactuallyload)
if (atstage()) if (atstage())
{ {
SCR_SetLoadingFile("newmap"); SCR_SetLoadingFile("newmap");
// if (!cl.worldmodel || cl.worldmodel->type == mod_dummy) // if (!cl.worldmodel || cl.worldmodel->type == mod_dummy)
// Host_EndGame("No worldmodel was loaded\n"); // Host_EndGame("No worldmodel was loaded\n");
Surf_NewMap (cl.worldmodel); Surf_NewMap (cl.worldmodel);
@ -3740,7 +3741,7 @@ void CL_ParseEstablished(void)
else else
security = "^["S_COLOR_RED"plain-text\\tip\\"CON_WARNING"Do not type passwords as they can potentially be seen by network sniffers^]"; 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);
} }
} }

View file

@ -63,7 +63,7 @@ void QDECL joyaxiscallback(cvar_t *var, char *oldvalue)
else if (!Q_strcasecmp(end, "right") || !Q_strcasecmp(end, "turnright")) else if (!Q_strcasecmp(end, "right") || !Q_strcasecmp(end, "turnright"))
var->ival = 4*sign; var->ival = 4*sign;
else if (!Q_strcasecmp(end, "left") || !Q_strcasecmp(end, "turnleft")) 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")) else if (!Q_strcasecmp(end, "up") || !Q_strcasecmp(end, "moveup"))
var->ival = 5*sign; var->ival = 5*sign;
else if (!Q_strcasecmp(end, "down") || !Q_strcasecmp(end, "movedown")) else if (!Q_strcasecmp(end, "down") || !Q_strcasecmp(end, "movedown"))

View file

@ -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; 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) #if !defined(FTE_TARGET_WEB) && defined(HAVE_DTLS)
{ {
extern cvar_t net_enable_dtls;
static const char *encoptions[] = static const char *encoptions[] =
{ {
"None", "Disabled",
"Accept", "Accept",
"Request", "Request",
"Require", "Require",
@ -1143,7 +1142,6 @@ void M_Menu_Network_f (void)
NULL NULL
}; };
#ifdef HAVE_DTLS #ifdef HAVE_DTLS
extern cvar_t net_enable_dtls;
static const char *dtlsopts[] = { static const char *dtlsopts[] = {
"Disabled", "Disabled",
"Accept", "Accept",

View file

@ -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. //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. //either way, custom protocols tend to require ftemaster/dpmaster so we want to heartbeat regardless.
#if defined(NQPROT) && !defined(QUAKETC) #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 #endif
{ {
//darkplaces here refers to the master server protocol, rather than the game protocol //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) if (sv_reportheartbeats.ival != 2 || !master->announced)
{ {
COM_Parse(master->cv.string); 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; master->announced = true;
} }
@ -414,7 +414,7 @@ static void SV_Master_SingleHeartbeat(net_masterlist_t *master)
struct thr_res struct thr_res
{ {
qboolean success; qboolean success;
netadr_t na[8]; netadr_t na[MAX_MASTER_ADDRESSES];
char str[1]; //trailing char str[1]; //trailing
}; };
static void SV_Master_Worker_Resolved(void *ctx, void *data, size_t a, size_t b) 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)) if (NET_AddrIsReliable(na))
{ {
struct dtlspeercred_s cred = {master->cv.string}; 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 //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); 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 //worker thread
static void SV_Master_Worker_Resolve(void *ctx, void *data, size_t a, size_t b) 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)); str = COM_ParseOut(str, token, sizeof(token));
if (*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) if (first && found)
break; //if we found one by name, don't try any fallback ip addresses. break; //if we found one by name, don't try any fallback ip addresses.
first = false; first = false;
} }
work->success = !!found; 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) void SV_Master_Heartbeat (void)
{ {
int i; 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()) if (sv_public.ival<=0 || SSV_IsSubServer())
return; return;
@ -605,6 +644,18 @@ void SV_Master_Heartbeat (void)
else else
SV_Master_SingleHeartbeat(&net_masterlist[i]); 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 #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 #ifdef WEBCLIENT
static void MasterInfo_ProcessHTTPInfo(serverinfo_t *srv, const char *info) static void MasterInfo_ProcessHTTPInfo(serverinfo_t *srv, const char *info)
{ {
char adrbuf[MAX_ADR_SIZE]; char adrbuf[MAX_ADR_SIZE];
if (info && (!(srv->status & SRVSTATUS_ALIVE) || srv->ping == PING_UNKNOWN)) 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->sends = 0; //no point pinging it, it won't work.
srv->ping = PING_UNKNOWN; srv->ping = PING_UNKNOWN;
srv->status |= SRVSTATUS_ALIVE; //or at least wouldn't have been reported this time around. 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->name, Info_ValueForKey(info, "hostname"), sizeof(srv->name));
Q_strncpyz(srv->gamedir, Info_ValueForKey(info, "modname"), sizeof(srv->gamedir)); Q_strncpyz(srv->gamedir, Info_ValueForKey(info, "modname"), sizeof(srv->gamedir));
Q_strncpyz(srv->map, Info_ValueForKey(info, "mapname"), sizeof(srv->map)); Q_strncpyz(srv->map, Info_ValueForKey(info, "mapname"), sizeof(srv->map));
srv->players = atoi(Info_ValueForKey(info, "clients")); srv->players = atoi(Info_ValueForKey(info, "clients"));
srv->maxplayers = atoi(Info_ValueForKey(info, "maxclients")); 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->numbots = 0;
srv->numhumans = srv->players - srv->numbots; srv->numhumans = srv->players - srv->numbots;
srv->freeslots = srv->maxplayers - srv->players; srv->freeslots = srv->maxplayers - srv->players;
@ -2564,7 +2677,7 @@ static void MasterInfo_ProcessHTTP(struct dl_download *dl)
char *el; char *el;
serverinfo_t *info; serverinfo_t *info;
char linebuffer[2048]; char linebuffer[2048];
char *brokerid; const char *brokerid;
char *infostring; char *infostring;
netadr_t brokeradr; netadr_t brokeradr;
@ -2611,12 +2724,19 @@ static void MasterInfo_ProcessHTTP(struct dl_download *dl)
if (*s == '#') //hash is a comment, apparently. if (*s == '#') //hash is a comment, apparently.
continue; continue;
for (infostring = s; *infostring && *infostring != ' '; ) for (infostring = s; *infostring && *infostring != ' ' && *infostring != '\t'; )
infostring++; infostring++;
if (*infostring == ' ') if (*infostring == ' ' || *infostring == '\t')
*infostring++ = 0; {
*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 else
infostring = NULL; infostring = NULL;
if (!strncmp(s, "ice:///", 7) || !strncmp(s, "ices:///", 8) || !strncmp(s, "rtc:///", 7) || !strncmp(s, "rtcs:///", 8)) if (!strncmp(s, "ice:///", 7) || !strncmp(s, "ices:///", 8) || !strncmp(s, "rtc:///", 7) || !strncmp(s, "rtcs:///", 8))
{ {
brokerid = s+((s[4]==':')?7:6); brokerid = s+((s[4]==':')?7:6);
@ -2624,10 +2744,14 @@ static void MasterInfo_ProcessHTTP(struct dl_download *dl)
if (!*brokerid) if (!*brokerid)
continue; //invalid... continue; //invalid...
} }
else if (*s == '/')
{
brokerid = s;
adr = brokeradr;
}
else else
{ {
brokerid = ""; if (!NET_StringToAdr2(s, 80, &adr, 1, &brokerid))
if (!NET_StringToAdr(s, 80, &adr))
continue; continue;
} }
@ -2648,8 +2772,8 @@ static void MasterInfo_ProcessHTTP(struct dl_download *dl)
info->special = 0; info->special = 0;
if (protocoltype == MP_QUAKEWORLD) if (protocoltype == MP_QUAKEWORLD)
info->special |= SS_QUAKEWORLD; info->special |= SS_QUAKEWORLD;
else if (protocoltype == MP_DPMASTER) else if (protocoltype == MP_DPMASTER) //actually ftemaster... so assume fteqw servers not ftenq ones unless otherwise indicated.
info->special |= SS_GETINFO; info->special |= SS_QUAKEWORLD|SS_GETINFO;
#if defined(Q2CLIENT) || defined(Q2SERVER) #if defined(Q2CLIENT) || defined(Q2SERVER)
else if (protocoltype == MP_QUAKE2) else if (protocoltype == MP_QUAKE2)
info->special |= SS_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))) else if (!strncmp(DISTRIBUTION, Info_ValueForKey(msg, "*version"), strlen(DISTRIBUTION)))
info->special |= SS_FTESERVER; info->special |= SS_FTESERVER;
info->protocol = strtoul(Info_ValueForKey(msg, "protocol"), &token, 0); if (!MasterInfo_ReadProtocol(info, msg))
if (info->protocol) { //try and guess.
{
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;
}
}
#ifdef Q2CLIENT #ifdef Q2CLIENT
else if (prototype == MP_QUAKE2) if (prototype == MP_QUAKE2)
info->special |= SS_QUAKE2; info->special |= SS_QUAKE2;
#endif #endif
#ifdef Q3CLIENT #ifdef Q3CLIENT
else if (prototype == MP_QUAKE3 || prototype == MP_DPMASTER/*if no protocol, assume q3 behaviours*/) else if (prototype == MP_QUAKE3 || prototype == MP_DPMASTER/*if no protocol, assume q3 behaviours*/)
info->special |= SS_QUAKE3; info->special |= SS_QUAKE3;
#endif #endif
#ifdef NQPROT #ifdef NQPROT
else if (prototype == MP_NETQUAKE) else if (prototype == MP_NETQUAKE)
info->special |= SS_NETQUAKE; info->special |= SS_NETQUAKE;
#endif #endif
else else
info->special |= SS_QUAKEWORLD; info->special |= SS_QUAKEWORLD;
}
if (favorite) //was specifically named, not retrieved from a master. if (favorite) //was specifically named, not retrieved from a master.
info->special |= SS_FAVORITE; info->special |= SS_FAVORITE;
@ -3786,16 +3868,19 @@ static void NetQ3_GlobalServers_Request(size_t masternum, int protocol, const ch
const char *url; const char *url;
struct dl_download *dl; struct dl_download *dl;
COM_Parse(com_protocolname.string); COM_Parse(com_protocolname.string);
if (!strncmp(net_ice_broker.string, "tls://", 6)) if (*net_ice_broker.string)
url = va("https://%s/raw/%s", net_ice_broker.string+6, com_token); {
else if (!strncmp(net_ice_broker.string, "tcp://", 6)) if (!strncmp(net_ice_broker.string, "tls://", 6))
url = va("http://%s/raw/%s", net_ice_broker.string+6, com_token); url = va("https://%s/raw/%s", net_ice_broker.string+6, com_token);
else else if (!strncmp(net_ice_broker.string, "tcp://", 6))
url = va("http://%s/raw/%s", net_ice_broker.string, com_token); 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); dl = HTTP_CL_Get(url, NULL, MasterInfo_ProcessHTTP);
if (dl) if (dl)
dl->isquery = true; dl->isquery = true;
}
} }
#endif #endif
#if POLLTOTALSOCKETS>0 #if POLLTOTALSOCKETS>0

View file

@ -213,7 +213,7 @@ void Sys_Printf (char *fmt, ...)
if (w >= 0xe000 && w < 0xe100) 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*/ /*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); putc(w&0x7f, out);
else if (w & 0x80) else if (w & 0x80)
{ {

View file

@ -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: 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: advertising dtls without a valid certificate will probably bug out if a client tries to auto-upgrade.
//FIXME: we don't cache server certs //FIXME: we don't cache server certs
#ifndef MASTERONLY #define HAVE_DTLS
#define HAVE_DTLS
#endif
#endif #endif
#if defined(USE_SQLITE) || defined(USE_MYSQL) #if defined(USE_SQLITE) || defined(USE_MYSQL)

View file

@ -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) qboolean CertLog_ConnectOkay(const char *hostname, void *cert, size_t certsize, unsigned int certlogproblems)
{ //this is specifically for dtls certs. { //this is specifically for dtls certs.
extern cvar_t net_enable_dtls;
struct certlog_s *l; struct certlog_s *l;
qboolean trusted = (net_enable_dtls.ival >= 2); qboolean trusted = (net_enable_dtls.ival >= 2);
char digest[DIGEST_MAXSIZE]; char digest[DIGEST_MAXSIZE];

View file

@ -152,17 +152,17 @@ int NET_LocalAddressForRemote(struct ftenet_connections_s *collection, netadr_
void NET_PrintAddresses(struct ftenet_connections_s *collection); void NET_PrintAddresses(struct ftenet_connections_s *collection);
qboolean NET_AddressSmellsFunny(netadr_t *a); qboolean NET_AddressSmellsFunny(netadr_t *a);
struct dtlspeercred_s; 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_TerminateRoute(struct ftenet_connections_s *collection, netadr_t *adr);
void NET_PrintConnectionsStatus(struct ftenet_connections_s *collection); void NET_PrintConnectionsStatus(struct ftenet_connections_s *collection);
enum addressscope_e enum addressscope_e
{ {
ASCOPE_PROCESS=0, ASCOPE_PROCESS=0, //unusable
ASCOPE_HOST=1, ASCOPE_HOST=1, //unroutable
ASCOPE_LINK=2, ASCOPE_LINK=2, //unpredictable
ASCOPE_LAN=3, ASCOPE_LAN=3, //private
ASCOPE_NET=4 ASCOPE_NET=4 //aka hopefully globally routable
}; };
enum addressscope_e NET_ClassifyAddress(netadr_t *adr, const char **outdesc); 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_SockadrToString (char *s, int len, struct sockaddr_qstorage *a, size_t sizeofa);
char *NET_BaseAdrToString (char *s, int len, netadr_t *a); 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); 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) #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); 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) #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 #ifdef HAVE_DTLS
struct dtlscred_s; struct dtlscred_s;
struct dtlsfuncs_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_Decode(struct ftenet_connections_s *col);
qboolean NET_DTLS_Disconnect(struct ftenet_connections_s *col, netadr_t *to); 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 dtls_psk_hint, dtls_psk_user, dtls_psk_key;
extern cvar_t net_enable_dtls;
#endif #endif
#ifdef SUPPORT_ICE #ifdef SUPPORT_ICE
neterr_t ICE_SendPacket(size_t length, const void *data, netadr_t *to); neterr_t ICE_SendPacket(size_t length, const void *data, netadr_t *to);

View file

@ -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 "quakedef.h"
#include "netinc.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. #define ASCOPE_TURN_REQUIRESCOPE ASCOPE_LAN //don't report loopback/link-local addresses to turn relays.
#endif #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 icecandidate_s
{ {
struct icecandinfo_s info; struct icecandinfo_s info;
@ -214,10 +224,22 @@ struct icestate_s
int id; int id;
char *name; char *name;
} codecslot[34]; //96-127. don't really need to care about other ones. } 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 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; quint16_t myport, peerport;
qboolean peerhasfwdtsn; qboolean peerhasfwdtsn;
double nextreinit; double nextreinit;
@ -271,12 +293,11 @@ static const struct
{"sha-384", &hash_sha2_384}, {"sha-384", &hash_sha2_384},
{"sha-512", &hash_sha2_512}, {"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 #endif
static neterr_t ICE_Transmit(void *cbctx, const qbyte *data, size_t datasize); 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 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 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; 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 = Z_Malloc(sizeof(*con));
con->conname = Z_StrDup(conname); 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->proto = proto;
con->rpwd = Z_StrDup(""); con->rpwd = Z_StrDup("");
con->rufrag = 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.type = NA_ICE;
con->qadr.prot = NP_DGRAM; con->qadr.prot = NP_DGRAM;
Q_strncpyz(con->qadr.address.icename, con->friendlyname, sizeof(con->qadr.address.icename)); Q_strncpyz(con->qadr.address.icename, con->friendlyname, sizeof(con->qadr.address.icename));
con->qadr.port = icenum;
con->mode = mode; 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) if (!con->sctp && (!con->sctpoptional || !con->peersctpoptional) && con->mysctpport && con->peersctpport)
{ {
con->sctp = Z_Malloc(sizeof(*con->sctp)); con->sctp = Z_Malloc(sizeof(*con->sctp));
con->sctp->icestate = con;
con->sctp->friendlyname = con->friendlyname;
con->sctp->myport = htons(con->mysctpport); con->sctp->myport = htons(con->mysctpport);
con->sctp->peerport = htons(con->peersctpport); con->sctp->peerport = htons(con->peersctpport);
con->sctp->o.tsn = rand() ^ (rand()<<16); 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, 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); 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-pwd:%s\n", con->lpwd), valuelen);
Q_strncatz(value, va("a=ice-ufrag:%s\n", con->lufrag), valuelen); Q_strncatz(value, va("a=ice-ufrag:%s\n", con->lufrag), valuelen);
@ -3065,7 +3100,7 @@ void ICE_Tick(void)
{ {
#ifdef HAVE_DTLS #ifdef HAVE_DTLS
if (con->sctp) 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) if (con->dtlsstate)
con->dtlsfuncs->Timeouts(con->dtlsstate); con->dtlsfuncs->Timeouts(con->dtlsstate);
#endif #endif
@ -3125,7 +3160,8 @@ icefuncs_t iceapi =
ICE_AddRCandidateInfo, ICE_AddRCandidateInfo,
ICE_Close, ICE_Close,
ICE_CloseModule, ICE_CloseModule,
ICE_GetLCandidateSDP ICE_GetLCandidateSDP,
ICE_Find
}; };
#endif #endif
@ -3208,9 +3244,12 @@ struct sctp_chunk_fwdtsn_s
} streams[];*/ } 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. { //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) if (peer->dtlsstate)
return peer->dtlsfuncs->Transmit(peer->dtlsstate, data, length); 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; 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]; qbyte pkt[65536];
size_t pktlen = 0; 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); pktlen += sizeof(*init) + sizeof(*ftsn);
h->crc = SCTP_Checksum(h, pktlen); h->crc = SCTP_Checksum(h, pktlen);
return SCTP_PeerSendPacket(peer, pktlen, h); return SCTP_PeerSendPacket(sctp, pktlen, h);
} }
else else
{ {
@ -3313,7 +3352,7 @@ static neterr_t SCTP_Transmit(sctp_t *sctp, struct icestate_s *peer, const void
pktlen += sizeof(*cookie) + sctp->cookiesize; pktlen += sizeof(*cookie) + sctp->cookiesize;
h->crc = SCTP_Checksum(h, pktlen); 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)) if (pktlen + sizeof(*d) + length >= 500 && length && pktlen != sizeof(*h))
{ //probably going to result in fragmentation issues. send separate packets. { //probably going to result in fragmentation issues. send separate packets.
h->crc = SCTP_Checksum(h, pktlen); h->crc = SCTP_Checksum(h, pktlen);
SCTP_PeerSendPacket(peer, pktlen, h); SCTP_PeerSendPacket(sctp, pktlen, h);
//reset to the header //reset to the header
pktlen = sizeof(*h); 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... return NETERR_SENT; //nothing to send...
h->crc = SCTP_Checksum(h, pktlen); 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... { //send an ack...
size_t pktlen = 0; size_t pktlen = 0;
struct sctp_header_s *h = (void*)resp; 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; sctp->qstreamid = sctp->i.r.sid;
if (net_ice_debug.ival >= 1) 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->dstport = sctp->peerport;
h->srcport = sctp->myport; 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; pktlen += sizeof(*d) + length;
h->crc = SCTP_Checksum(h, pktlen); 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 cause;
quint16_t length; 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; quint16_t cc, cl;
while(totallen > 0) 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) 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 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", peer->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", peer->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", peer->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", peer->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", peer->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", peer->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", peer->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", peer->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", peer->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", peer->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", peer->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", peer->friendlyname, errortype, (char*)(s+1)); 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", peer->friendlyname, errortype); break; default: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Unknown Reason\n", sctp->friendlyname, errortype); break;
} }
totallen -= cl; 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]; 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_message.cursize&3)
{ {
if (net_ice_debug.ival >= 2) 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. 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 (adv >= SCTP_RCVSIZE)
{ {
if (net_ice_debug.ival >= 1) 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) else if (adv <= 0)
{ {
if (net_ice_debug.ival >= 2) 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)) else if (sctp->i.received[(tsn>>3)%sizeof(sctp->i.received)] & 1<<(tsn&7))
{ {
if (net_ice_debug.ival >= 2) 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 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 (sctp->i.r.size + dlen > sizeof(sctp->i.r.buf))
{ {
if (net_ice_debug.ival >= 2) 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. sctp->i.r.toobig = true; //reassembled packet was too large, just corrupt it.
} }
else 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)) 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; break;
default: default:
if (net_ice_debug.ival >= 2) 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; break;
} }
p = (void*)((qbyte*)p + ((plen+3)&~3)); 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; sctp->nextreinit = 0;
if (sctp->cookie) 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 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. //complete. calc the proper crc and send it off.
rh->crc = SCTP_Checksum(rh, end-resp); rh->crc = SCTP_Checksum(rh, end-resp);
SCTP_PeerSendPacket(peer, end-resp, rh); SCTP_PeerSendPacket(sctp, end-resp, rh);
} }
} }
break; 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. //complete. calc the proper crc and send it off.
pongh->crc = SCTP_Checksum(pongh, sizeof(*pongh) + clen); 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); Z_Free(pongh);
} }
break; break;
// case SCTP_TYPE_PONG: //we don't send pings // case SCTP_TYPE_PONG: //we don't send pings
case SCTP_TYPE_ABORT: case SCTP_TYPE_ABORT:
SCTP_ErrorChunk(peer, "Abort", (struct sctp_errorcause_s*)(c+1), clen-sizeof(*c)); SCTP_ErrorChunk(sctp, "Abort", (struct sctp_errorcause_s*)(c+1), clen-sizeof(*c));
ICE_Set(peer, "state", STRINGIFY(ICE_FAILED)); if (sctp->icestate)
ICE_Set(sctp->icestate, "state", STRINGIFY(ICE_FAILED));
break; break;
case SCTP_TYPE_SHUTDOWN: //FIXME. we should send an ack... 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) 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; break;
// case SCTP_TYPE_SHUTDOWNACK: //we don't send shutdowns, cos we're lame like that... // case SCTP_TYPE_SHUTDOWNACK: //we don't send shutdowns, cos we're lame like that...
case SCTP_TYPE_ERROR: case SCTP_TYPE_ERROR:
//not fatal... //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; break;
case SCTP_TYPE_COOKIEECHO: case SCTP_TYPE_COOKIEECHO:
if (clen >= sizeof(struct sctp_chunk_s)) 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. //complete. calc the proper crc and send it off.
rh->crc = SCTP_Checksum(rh, end-resp); 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. 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: safedefault:
//no idea what this chunk is, just ignore it... //no idea what this chunk is, just ignore it...
if (net_ice_debug.ival >= 1) 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; break;
} }
c = (struct sctp_chunk_s*)((qbyte*)c + ((clen+3)&~3)); //next chunk is 4-byte aligned. c = (struct sctp_chunk_s*)((qbyte*)c + ((clen+3)&~3)); //next chunk is 4-byte aligned.
} }
if (sctp->i.ackneeded >= 5) 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. //we already made sense of it all.
net_message.cursize = 0; 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 #endif
@ -4031,9 +4236,24 @@ qboolean ICE_WasStun(ftenet_connections_t *col)
if (con->state == ICE_CONNECTING) if (con->state == ICE_CONNECTING)
ICE_Set(con, "state", STRINGIFY(ICE_CONNECTED)); 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; return true;
} }
@ -4465,6 +4685,7 @@ qboolean ICE_WasStun(ftenet_connections_t *col)
unsigned int tielow = 0; unsigned int tielow = 0;
qboolean usecandidate = false; qboolean usecandidate = false;
unsigned int priority = 0; unsigned int priority = 0;
char *lpwd = NULL;
#endif #endif
char *integritypos = NULL; char *integritypos = NULL;
int error = 0; int error = 0;
@ -4645,6 +4866,7 @@ qboolean ICE_WasStun(ftenet_connections_t *col)
if (con->state == ICE_CONNECTING) if (con->state == ICE_CONNECTING)
ICE_Set(con, "state", STRINGIFY(ICE_CONNECTED)); ICE_Set(con, "state", STRINGIFY(ICE_CONNECTED));
} }
lpwd = con->lpwd;
} }
}//otherwise its just an ip check }//otherwise its just an ip check
else else
@ -4736,13 +4958,13 @@ qboolean ICE_WasStun(ftenet_connections_t *col)
MSG_WriteChar(&buf, 0); MSG_WriteChar(&buf, 0);
#ifdef SUPPORT_ICE #ifdef SUPPORT_ICE
if (con) if (lpwd)
{ {
//message integrity is a bit annoying //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[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; data[3] = ((buf.cursize+4+sizeof(integrity)-20)>>0)&0xff;
//but the hash is to the start of the attribute's header //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(STUNATTR_MESSAGEINTEGRITIY));
MSG_WriteShort(&buf, BigShort(sizeof(integrity))); //sha1 key length MSG_WriteShort(&buf, BigShort(sizeof(integrity))); //sha1 key length
SZ_Write(&buf, integrity, sizeof(integrity)); //integrity data 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; data[3] = ((buf.cursize-20)>>0)&0xff;
#ifdef SUPPORT_ICE #ifdef SUPPORT_ICE
TURN_Encapsulate(con, &net_from, data, buf.cursize); if (con)
#else TURN_Encapsulate(con, &net_from, data, buf.cursize);
NET_SendPacket(col, buf.cursize, data, &net_from); else
#endif #endif
NET_SendPacket(col, buf.cursize, data, &net_from);
return true; return true;
} }
} }
@ -4803,7 +5026,7 @@ qboolean ICE_WasStun(ftenet_connections_t *col)
net_from = con->qadr; net_from = con->qadr;
#ifdef HAVE_DTLS #ifdef HAVE_DTLS
if (con->sctp) if (con->sctp)
SCTP_Decode(con->sctp, con, col); SCTP_Decode(con->sctp, col);
else else
#endif #endif
if (net_message.cursize) if (net_message.cursize)
@ -4862,9 +5085,9 @@ neterr_t ICE_SendPacket(size_t length, const void *data, netadr_t *to)
return NETERR_DISCONNECTED; return NETERR_DISCONNECTED;
#ifdef HAVE_DTLS #ifdef HAVE_DTLS
if (con->sctp) if (con->sctp)
return SCTP_Transmit(con->sctp, con, data, length); return SCTP_Transmit(con->sctp, data, length);
if (con->dtlsstate) if (con->dtlsstate)
return SCTP_PeerSendPacket(con, length, data); return con->dtlsfuncs->Transmit(con->dtlsstate, data, length);
#endif #endif
if (con->chosenpeer.type != NA_INVALID) if (con->chosenpeer.type != NA_INVALID)
return ICE_Transmit(con, data, length); 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; 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 { //sends offer
struct icestate_s *ice; struct icestate_s *ice;
qboolean usewebrtc; qboolean usewebrtc;
@ -5062,7 +5285,7 @@ static void FTENET_ICE_Establish(ftenet_ice_connection_t *b, int cl, struct ices
else else
usewebrtc = net_ice_usewebrtc.ival; usewebrtc = net_ice_usewebrtc.ival;
#endif #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) if (!*ret)
return; //some kind of error?!? return; //some kind of error?!?
@ -5259,12 +5482,12 @@ handleerror:
{ {
b->error = true; b->error = true;
if (net_ice_debug.ival) if (net_ice_debug.ival)
Con_Printf(S_COLOR_GRAY"[%s]: Broker lost connection: %s\n", b->ice?b->ice->friendlyname:"?", *data?data:"<NO REASON>"); Con_Printf(S_COLOR_GRAY"[%s]: Broker host lost connection: %s\n", b->ice?b->ice->friendlyname:"?", *data?data:"<NO REASON>");
} }
else if (cl >= 0 && cl < b->numclients) else if (cl >= 0 && cl < b->numclients)
{ {
if (net_ice_debug.ival) 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:"<NO REASON>"); Con_Printf(S_COLOR_GRAY"[%s]: Broker client lost connection: %s\n", b->clients[cl].ice?b->clients[cl].ice->friendlyname:"?", *data?data:"<NO REASON>");
if (b->clients[cl].ice) if (b->clients[cl].ice)
iceapi.Close(b->clients[cl].ice, false); iceapi.Close(b->clients[cl].ice, false);
b->clients[cl].ice = NULL; b->clients[cl].ice = NULL;
@ -5291,7 +5514,7 @@ handleerror:
} }
if (cl >= 0 && cl < b->numclients) 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) if (net_ice_debug.ival)
Con_Printf(S_COLOR_GRAY"[%s]: New client spotted...\n", b->clients[cl].ice?b->clients[cl].ice->friendlyname:"?"); 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 else
{ {
// Con_DPrintf("Server found: %s\n", data); // 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; b->serverid = cl;
if (net_ice_debug.ival) if (net_ice_debug.ival)
@ -5526,4 +5749,134 @@ void ICE_Init(void)
Cvar_Register(&net_ice_debug, "networking"); Cvar_Register(&net_ice_debug, "networking");
Cmd_AddCommand("net_ice_show", ICE_Show_f); 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 #endif

File diff suppressed because it is too large Load diff

View file

@ -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 *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 *Close)(struct icestate_s *con, qboolean force); //bye then.
void (QDECL *CloseModule)(void *module); //closes all unclosed connections, with warning. 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. 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; } icefuncs_t;
extern icefuncs_t iceapi; extern icefuncs_t iceapi;
extern cvar_t net_ice_broker; extern cvar_t net_ice_broker;
@ -435,6 +435,9 @@ typedef struct ftenet_connections_s
size_t cursize; size_t cursize;
qbyte data[1]; qbyte data[1];
} *delayed_packets; } *delayed_packets;
netadr_t srflx[2]; //ipv4, ipv6
unsigned int srflx_tid[3]; //to verify the above.
} ftenet_connections_t; } ftenet_connections_t;
void ICE_Tick(void); void ICE_Tick(void);

View file

@ -254,6 +254,16 @@ char *Z_StrDupf(const char *format, ...)
return string; 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) void Z_StrCat(char **ptr, const char *append)
{ {
size_t oldlen = *ptr?strlen(*ptr):0; size_t oldlen = *ptr?strlen(*ptr):0;

View file

@ -151,6 +151,7 @@ void QDECL ZG_FreeGroup(zonegroup_t *ctx);
char *Z_StrDupf(const char *format, ...); char *Z_StrDupf(const char *format, ...);
void Z_StrCat(char **ptr, const char *append); 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 void *Hunk_Alloc (int size); // returns 0 filled memory

View file

@ -305,7 +305,7 @@ const char *HTTP_RunClient (HTTP_active_connections_t *cl)
char *content; char *content;
char *msg, *nl; char *msg, *nl;
char buf2[2560]; //short lived temp buffer. char buf2[2560]; //short lived temp buffer.
char resource[2560]; char resource[2560], *args;
char host[256]; char host[256];
char mode[80]; char mode[80];
qboolean hostspecified; 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, "/")) if (!strcmp(resource, "/"))
strcpy(resource, "/index.html"); strcpy(resource, "/index.html");

View file

@ -6462,18 +6462,31 @@ char *PF_infokey_Internal (int entnum, const char *key)
sprintf(ov, "%d", SV_CalcPing (&svs.clients[entnum-1], true)); sprintf(ov, "%d", SV_CalcPing (&svs.clients[entnum-1], true));
else if (!strcmp(key, "guid")) else if (!strcmp(key, "guid"))
sprintf(ov, "%s", pl->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")) else if (!strcmp(key, "*cert_dn"))
NET_GetConnectionCertificate(svs.sockets, &controller->netchan.remote_address, QCERT_PEERSUBJECT, ov, sizeof(ov)); 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")) else if (!strcmp(key, "challenge"))
sprintf(ov, "%u", pl->challenge); sprintf(ov, "%u", pl->challenge);
else if (!strcmp(key, "*userid")) else if (!strcmp(key, "*userid"))

View file

@ -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. 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 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 //speed cheat testing
#define NEWSPEEDCHEATPROT #define NEWSPEEDCHEATPROT
float msecs; float msecs;
@ -1385,10 +1386,12 @@ void SV_SendClientPrespawnInfo(client_t *client);
void SV_ClientProtocolExtensionsChanged(client_t *client); void SV_ClientProtocolExtensionsChanged(client_t *client);
//sv_master.c //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); 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_AddBrokerGame(const char *brokerid, const char *info);
void SVM_RemoveBrokerGame(const char *brokerid); 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);
// //

View file

@ -2160,9 +2160,6 @@ static void SV_Status_f (void)
#ifdef QWOVERQ3 #ifdef QWOVERQ3
extern cvar_t sv_listen_q3; extern cvar_t sv_listen_q3;
#endif #endif
#ifdef HAVE_DTLS
extern cvar_t net_enable_dtls;
#endif
#ifndef SERVERONLY #ifndef SERVERONLY
if (!sv.state && cls.state >= ca_connected && !cls.demoplayback && cls.protocol == CP_NETQUAKE) 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"; s = "private";
Con_TPrintf("public : %s\n", s); 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) switch(svs.gametype)
{ {
#ifdef Q3SERVER #ifdef Q3SERVER

View file

@ -140,7 +140,6 @@ cvar_t sv_listen_q3 = CVAR("sv_listen_q3", "0");
#endif #endif
cvar_t sv_reconnectlimit = CVARD("sv_reconnectlimit", "0", "Blocks dupe connection within the specified length of time ."); 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."); 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_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_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."); 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 #ifdef SV_MASTER
cvar_t sv_master = CVAR("sv_master", "0"); cvar_t sv_master = CVAR("sv_master", "0");
cvar_t sv_masterport = CVAR("sv_masterport", "0");
#endif #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."); 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.mtu = info->mtu;
newcl->netchan.message.maxsize = sizeof(newcl->netchan.message_buf); newcl->netchan.message.maxsize = sizeof(newcl->netchan.message_buf);
#ifdef HAVE_ICE
if (info->adr.type == NA_ICE) if (info->adr.type == NA_ICE)
newcl->netchan.mtu -= 48+12; //dtls+sctp overhead 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 newcl->netchan.mtu -= 48; //dtls overhead
} }
else 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 //returns false to block replies
//this is to mitigate wasted bandwidth if we're used as a udp amplification //this is to mitigate wasted bandwidth if we're used as a udp amplification
qboolean SVC_ThrottleInfo (void) qboolean SVC_ThrottleInfo (void)
@ -4083,7 +4089,7 @@ static struct attacker_s
} *dosattacker; } *dosattacker;
static size_t dosattacker_count; static size_t dosattacker_count;
static size_t dosattacker_max; 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_period 30 //within Y secs
#define dosattacker_blocktime (60*60*24) //block them for Z secs (24 hours). #define dosattacker_blocktime (60*60*24) //block them for Z secs (24 hours).
static qboolean SV_DetectAmplificationDDOS (void) static qboolean SV_DetectAmplificationDDOS (void)
@ -4286,8 +4292,10 @@ qboolean SV_ConnectionlessPacket (void)
else else
{ {
//NET_DTLS_Disconnect(svs.sockets, &net_from); //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"); Netchan_OutOfBandPrint(NS_SERVER, &net_from, "dtlsopened");
else
SV_RejectMessage (SCP_QUAKEWORLD, "DTLS driver failure.\n");
} }
} }
else else
@ -4332,6 +4340,13 @@ qboolean SV_ConnectionlessPacket (void)
} }
else if (!strcmp(c, "realip") || !strcmp(c, "ip")) else if (!strcmp(c, "realip") || !strcmp(c, "ip"))
SVC_RealIP (); 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")) else if (!strcmp(c,"lastscores"))
{ {
@ -5077,10 +5092,6 @@ qboolean SV_ReadPackets (float *delay)
NET_ReadPackets(svs.sockets); NET_ReadPackets(svs.sockets);
#ifdef HAVE_DTLS
NET_DTLS_Timeouts(svs.sockets);
#endif
if (inboundsequence == oldinboundsequence) if (inboundsequence == oldinboundsequence)
return false; //nothing new. return false; //nothing new.
oldinboundsequence = inboundsequence; oldinboundsequence = inboundsequence;
@ -5513,12 +5524,7 @@ float SV_Frame (void)
#ifdef SV_MASTER #ifdef SV_MASTER
if (sv_master.ival) if (sv_master.ival)
{ SVM_Think();
if (sv_masterport.ival)
SVM_Think(sv_masterport.ival);
else
SVM_Think(PORT_QWMASTER);
}
#endif #endif
#ifdef PLUGINS #ifdef PLUGINS
@ -5882,7 +5888,6 @@ void SV_InitLocal (void)
Cvar_Register (&sv_banproxies, cvargroup_serverpermissions); Cvar_Register (&sv_banproxies, cvargroup_serverpermissions);
#ifdef SV_MASTER #ifdef SV_MASTER
Cvar_Register (&sv_master, cvargroup_servercontrol); Cvar_Register (&sv_master, cvargroup_servercontrol);
Cvar_Register (&sv_masterport, cvargroup_servercontrol);
#endif #endif
Cvar_Register (&filterban, cvargroup_servercontrol); Cvar_Register (&filterban, cvargroup_servercontrol);

View file

@ -45,6 +45,7 @@ typedef struct svm_server_s {
unsigned int bots; //non-human players unsigned int bots; //non-human players
unsigned int clients; //human players unsigned int clients; //human players
unsigned int maxclients; //limit of bots+clients, but not necessarily spectators. unsigned int maxclients; //limit of bots+clients, but not necessarily spectators.
int secure:1;
int needpass:1; int needpass:1;
int coop:1; int coop:1;
char hostname[64]; //just for our own listings. char hostname[64]; //just for our own listings.
@ -189,6 +190,28 @@ static svm_server_t *SVM_GetServer(netadr_t *adr)
} }
return NULL; 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) 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 if (game->numservers || !sv_hideinactivegames.ival) //only show active servers
{ {
QuakeCharsToHTML(tmpbuf, sizeof(tmpbuf), game->name, true); QuakeCharsToHTML(tmpbuf, sizeof(tmpbuf), game->name, true);
VFS_PRINTF(f, "<tr><td><a href=\"game/%s%s%s\">%s</a></td><td>%u player%s", game->name, query?"?":"", query?query:"", tmpbuf, clients, clients==1?"":"s"); VFS_PRINTF(f, "<tr><td><a href=\"/game/%s%s%s\">%s</a></td><td>%u player%s", game->name, query?"?":"", query?query:"", tmpbuf, clients, clients==1?"":"s");
if (bots) if (bots)
VFS_PRINTF(f, ", %u bot%s", bots, bots==1?"":"s"); VFS_PRINTF(f, ", %u bot%s", bots, bots==1?"":"s");
if (specs) 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 *SVM_Generate_Serverinfo(const char **mimetype, const char *serveraddr, const char *query)
{ {
vfsfile_t *f = VFSPIPE_Open(1, false); vfsfile_t *f = VFSPIPE_Open(1, false);
char tmpbuf[256]; char tmpbuf[512];
char hostname[1024]; char hostname[1024];
svm_server_t *server; svm_server_t *server;
netadr_t adr[64]; netadr_t adr[64];
size_t count, u; size_t count, u;
const char *url; const char *url, *fp;
VFS_PRINTF(f, "%s", master_css); VFS_PRINTF(f, "%s", master_css);
VFS_PRINTF(f, "<h1>Single Server Info</h1>\n"); VFS_PRINTF(f, "<h1>Single Server Info</h1>\n");
@ -656,10 +679,17 @@ vfsfile_t *SVM_Generate_Serverinfo(const char **mimetype, const char *serveraddr
QuakeCharsToHTML(hostname, sizeof(hostname), server->hostname, false); QuakeCharsToHTML(hostname, sizeof(hostname), server->hostname, false);
url = NET_AdrToString(tmpbuf, sizeof(tmpbuf), &server->adr); 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) if (server->game->scheme && !server->brokerid)
url = va("<a href=\"%s://%s\">%s</a>", server->game->scheme, url, url); url = va("<a href=\"%s://%s%s\">%s</a>", server->game->scheme, url,fp, url);
VFS_PRINTF(f, "<tr><td>%s</td><td>%s</td><td>%s%s</td><td>%s</td><td>%s</td><td>%u/%u</td></tr>\n", server->game?server->game->name:"Unknown", url, (server->needpass&1)?"&#x1F512;":"", hostname, server->gamedir, server->mapname, server->clients, server->maxclients); VFS_PRINTF(f, "<tr><td><a href=\"/game/%s%s%s\">%s</a></td><td>%s</td><td>%s%s%s%s</td><td>%s</td><td>%s</td><td>%u/%u</td></tr>\n",
server->game?server->game->name:"Unknown", query?"?":"", query?query:"", server->game?server->game->name:"Unknown", //game column
url, //address column
server->secure?"&#x1f6e1;":"&#x1f6ab;", (server->needpass&1)?"&#x1F512;":"", (server->coop&1)?"&#x1F6B8;":"", hostname, //hostname column
server->gamedir, server->mapname, server->clients, server->maxclients);
VFS_PRINTF(f, "</table>\n"); VFS_PRINTF(f, "</table>\n");
VFS_PRINTF(f, "<br/>\n"); VFS_PRINTF(f, "<br/>\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); infourl = url = NET_AdrToString(tmpbuf, sizeof(tmpbuf), &server->adr);
} }
QuakeCharsToHTML(hostname, sizeof(hostname), server->hostname, false); QuakeCharsToHTML(hostname, sizeof(hostname), server->hostname, false);
VFS_PRINTF(f, "<tr><td><a href=\"/server/%s\">%s</a></td><td>%s%s%s</td><td>%s</td><td>%s</td><td>%u", infourl, url, (server->needpass&1)?"&#x1F512;":"", (server->coop&1)?"&#x1F6B8;":"", hostname, server->gamedir, server->mapname, server->clients); VFS_PRINTF(f, "<tr><td><a href=\"/server/%s\">%s</a></td><td>%s%s%s%s</td><td>%s</td><td>%s</td><td>%u", infourl, url, server->secure?"&#x1f6e1;":"&#x1f6ab;", (server->needpass&1)?"&#x1F512;":"", (server->coop&1)?"&#x1F6B8;":"", hostname, server->gamedir, server->mapname, server->clients);
if (server->bots) if (server->bots)
VFS_PRINTF(f, "+%ub", server->bots); VFS_PRINTF(f, "+%ub", server->bots);
VFS_PRINTF(f, "/%u", server->maxclients); 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_game_t *game;
svm_server_t *server; svm_server_t *server;
vfsfile_t *f = VFSPIPE_Open(1, false); 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)); COM_StripExtension(gamename, tmpbuf, sizeof(tmpbuf));
game = SVM_FindGame(tmpbuf, false); 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); VFS_PRINTF(f, "#Server list for \"%s\"\n", tmpbuf);
for (server = (game?game->firstserver:NULL); server; server = server->next) 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) 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 else
VFS_PRINTF(f, "%s\n", NET_AdrToString(tmpbuf, sizeof(tmpbuf), &server->adr)); 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; *(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)) if (NET_WasSpecialPacket(svm_sockets))
{ {
svm.total.stun++; svm.total.stun++;
@ -1258,7 +1304,6 @@ static void SVM_ProcessUDPPacket(void)
SVM_GenChallenge(ourchallenge, sizeof(ourchallenge), &net_from); SVM_GenChallenge(ourchallenge, sizeof(ourchallenge), &net_from);
if (!strcmp(chal, ourchallenge)) if (!strcmp(chal, ourchallenge))
{ {
bots = atoi(Info_ValueForKey(s, "bots")); bots = atoi(Info_ValueForKey(s, "bots"));
clients = atoi(Info_ValueForKey(s, "clients")); clients = atoi(Info_ValueForKey(s, "clients"));
clients = max(0, clients-bots); clients = max(0, clients-bots);
@ -1272,11 +1317,13 @@ static void SVM_ProcessUDPPacket(void)
if (srv) if (srv)
{ {
Q_strncpyz(srv->rules, s, sizeof(srv->rules)); Q_strncpyz(srv->rules, s, sizeof(srv->rules));
Info_RemoveKey(srv->rules, "challenge"); //prevent poisoning
if (developer.ival) if (developer.ival)
Info_Print(s, "\t"); Info_Print(s, "\t");
if (game) if (game)
srv->protover = atoi(Info_ValueForKey(s, "protocol")); srv->protover = atoi(Info_ValueForKey(s, "protocol"));
srv->maxclients = atoi(Info_ValueForKey(s, "sv_maxclients")); srv->maxclients = atoi(Info_ValueForKey(s, "sv_maxclients"));
srv->secure = !!*Info_ValueForKey(s, "*fp");
srv->needpass = atoi(Info_ValueForKey(s, "needpass")); srv->needpass = atoi(Info_ValueForKey(s, "needpass"));
srv->coop = atoi(Info_ValueForKey(s, "coop")); srv->coop = atoi(Info_ValueForKey(s, "coop"));
if (!srv->coop) if (!srv->coop)
@ -1453,6 +1500,7 @@ static void SVM_ProcessUDPPacket(void)
Info_Print(s, "\t"); Info_Print(s, "\t");
srv->protover = 3;//atoi(Info_ValueForKey(s, "protocol")); srv->protover = 3;//atoi(Info_ValueForKey(s, "protocol"));
srv->maxclients = atoi(Info_ValueForKey(s, "maxclients")); srv->maxclients = atoi(Info_ValueForKey(s, "maxclients"));
srv->secure = !!*Info_ValueForKey(s, "*fp");
srv->needpass = atoi(Info_ValueForKey(s, "needpass")); srv->needpass = atoi(Info_ValueForKey(s, "needpass"));
srv->coop = atoi(Info_ValueForKey(s, "coop")); srv->coop = atoi(Info_ValueForKey(s, "coop"));
if (!srv->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. //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). //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 else
svm.total.junk++; svm.total.junk++;
} }
@ -1577,14 +1633,123 @@ float SVM_RequerySlaves(void)
return 4; //nothing happening. 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); NET_ReadPackets (svm_sockets);
SVM_RemoveOldServers(); SVM_RemoveOldServers();
return SVM_RequerySlaves(); return SVM_RequerySlaves();
} }
#else #else
float SVM_Think(int port){return 4;} float SVM_Think(void){return 4;}
#endif #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) void SV_Init (struct quakeparms_s *parms)
{ {
int manarg; int manarg;
size_t u;
COM_InitArgv (parms->argc, parms->argv); 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 ("quit", SV_Quit_f);
Cmd_AddCommand ("status", SVM_Status_f); Cmd_AddCommand ("status", SVM_Status_f);
Cmd_AddCommand ("gamealias", SVM_GameAlias_f);
svm_sockets = FTENET_CreateCollection(true, SVM_ProcessUDPPacket); SVM_Register();
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");
Cvar_ParseWatches(); Cvar_ParseWatches();
host_initialized = true; host_initialized = true;
@ -1760,11 +1839,7 @@ void SV_Init (struct quakeparms_s *parms)
Cmd_StuffCmds(); Cmd_StuffCmds();
Cbuf_Execute (); Cbuf_Execute ();
Cvar_ForceCallback(&sv_masterport); SVM_Begin();
Cvar_ForceCallback(&sv_masterport_tcp);
SVM_FoundManifest(NULL, fs_manifest);
FS_EnumerateKnownGames(SVM_FoundManifest, NULL);
Con_Printf ("Exe: %s\n", version_string()); Con_Printf ("Exe: %s\n", version_string());
@ -1785,7 +1860,7 @@ float SV_Frame (void)
} }
Cbuf_Execute (); Cbuf_Execute ();
sleeptime = SVM_Think(sv_masterport.ival); sleeptime = SVM_Think();
//record lots of info over multiple frames, for smoother stats info. //record lots of info over multiple frames, for smoother stats info.
svm.total.timestamp = realtime; svm.total.timestamp = realtime;

View file

@ -468,7 +468,7 @@ int SV_MVD_GotQTVRequest(vfsfile_t *clientstream, char *headerstart, char *heade
{ {
char tmp[32]; char tmp[32];
Sys_RandomBytes(tmp, sizeof(tmp)); 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" e = va("QTVSV 1\n"

View file

@ -3499,8 +3499,7 @@ void SV_SendClientMessages (void)
stepmsec = 13; stepmsec = 13;
cmd.msec = stepmsec; cmd.msec = stepmsec;
if (sv_showpredloss.ival) c->hoverms += cmd.msec;
Con_Printf("%s: forcing %g msecs (anti-hover)\n", c->name, cmd.msec);
VectorCopy(c->lastcmd.angles, cmd.angles); VectorCopy(c->lastcmd.angles, cmd.angles);
cmd.buttons = c->lastcmd.buttons; cmd.buttons = c->lastcmd.buttons;
SV_RunCmd (&cmd, true); SV_RunCmd (&cmd, true);

View file

@ -7240,6 +7240,12 @@ void SV_RunCmd (usercmd_t *ucmd, qboolean recurse)
#ifdef NEWSPEEDCHEATPROT #ifdef NEWSPEEDCHEATPROT
if (ucmd->msec && host_client->msecs > 500) if (ucmd->msec && host_client->msecs > 500)
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) if (ucmd->msec > host_client->msecs)
{ //they're over their timeslice allocation { //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. //if they're not taking the piss then be prepared to truncate the frame. this should hide clockskew without allowing full-on speedcheats.

View file

@ -794,6 +794,8 @@ mergeInto(LibraryManager.library,
return -1; return -1;
if (s.con == 0) if (s.con == 0)
return 0; //not connected yet return 0; //not connected yet
if (s.err != 0)
return -1;
if (len == 0) if (len == 0)
return 0; //... return 0; //...
s.ws.send(HEAPU8.subarray(data, data+len)); s.ws.send(HEAPU8.subarray(data, data+len));
@ -888,6 +890,29 @@ mergeInto(LibraryManager.library,
s.recvchan.binaryType = 'arraybuffer'; s.recvchan.binaryType = 'arraybuffer';
s.recvchan.onmessage = s.ws.onmessage; 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) if (clientside)
{ {

View file

@ -49,12 +49,50 @@ qboolean Sys_RandomBytes(qbyte *string, int len)
void Sys_Printf (char *fmt, ...) void Sys_Printf (char *fmt, ...)
{ {
va_list argptr; 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); va_start (argptr,fmt);
vsnprintf (buf, sizeof(buf), fmt, argptr); vsnprintf (text, sizeof(text), fmt, argptr);
emscriptenfte_print(buf);
va_end (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 #if 1