From fe28099e68f2fcd6c35a7e53f17c89be31ce286f Mon Sep 17 00:00:00 2001 From: Spoike Date: Wed, 26 Feb 2020 00:37:52 +0000 Subject: [PATCH] Switch to using epoll on linux, because we can. Rework q3bsp_mergedlightmaps as q3bsp_mergelightmaps. Now a boolean filling to the gpu's limit. Now also fills horizontally too. ftemaster now provides needpass info for sv_public 2 servers. fix (most?) ftemaster crashes. ftemaster now supports protocol name aliases (allowing for more friendly game names in its html). ftemaster now pings the servers from a different port. This should highlight/exclude servers that are unreachable for nat/firewall reasons. Fix memory leak from mvd recording. Servers should now cope better with ctrl-z and related fg/bg unix shell commands. git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5638 fc73d0e0-1445-4013-8a0c-d673dee63da5 --- engine/client/cl_demo.c | 18 - engine/client/cl_main.c | 284 +++--- engine/client/client.h | 3 +- engine/client/image.c | 29 +- engine/client/net_master.c | 4 +- engine/client/renderer.c | 4 + engine/client/snd_al.c | 2 +- engine/common/com_mesh.c | 2 +- engine/common/common.h | 1 + engine/common/fs.c | 17 +- engine/common/fs_vpk.c | 6 +- engine/common/gl_q2bsp.c | 119 ++- engine/common/net.h | 2 +- engine/common/net_ice.c | 24 +- engine/common/net_ssl_gnutls.c | 33 +- engine/common/net_wins.c | 1658 ++++++++++++++++++-------------- engine/common/netinc.h | 38 +- engine/common/zone.c | 16 +- engine/gl/gl_backend.c | 1 + engine/gl/gl_font.c | 2 +- engine/gl/gl_model.c | 17 +- engine/gl/gl_model.h | 3 +- engine/gl/gl_rsurf.c | 2 + engine/http/httpclient.c | 2 +- engine/server/server.h | 1 + engine/server/sv_main.c | 392 ++++---- engine/server/sv_master.c | 775 ++++++++++----- engine/server/sv_mvd.c | 23 +- engine/server/sv_sys_unix.c | 115 ++- plugins/xsv/qux.h | 1 - 30 files changed, 2126 insertions(+), 1468 deletions(-) diff --git a/engine/client/cl_demo.c b/engine/client/cl_demo.c index 164199066..24891d817 100644 --- a/engine/client/cl_demo.c +++ b/engine/client/cl_demo.c @@ -976,24 +976,6 @@ readit: return 1; } -/* -==================== -CL_GetMessage - -Handles recording and playback of demos, on top of NET_ code -==================== -*/ -qboolean CL_GetMessage (void) -{ - if (cls.demoplayback != DPB_NONE) - return CL_GetDemoMessage (); - - if (NET_GetPacket (cls.sockets, 0) < 0) - return false; - - return true; -} - /* ==================== CL_Stop_f diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c index 986c9dce4..3c6ec9589 100644 --- a/engine/client/cl_main.c +++ b/engine/client/cl_main.c @@ -743,6 +743,12 @@ char *CL_TryingToConnect(void) return cls.servername; } +#ifdef NQPROT +static void CL_NullReadPacket(void) +{ //just drop it all +} +#endif + /* ================= CL_CheckForResend @@ -972,9 +978,10 @@ void CL_CheckForResend (void) NET_AdrToString(data, sizeof(data), &connectinfo.adr); /*eat up the server's packets, to clear any lingering loopback packets (like disconnect commands... yes this might cause packetloss for other clients)*/ - while(NET_GetPacket (svs.sockets, 0) >= 0) - { - } + svs.sockets->ReadGamePacket = CL_NullReadPacket; + NET_ReadPackets(svs.sockets); + svs.sockets->ReadGamePacket = SV_ReadPacket; + net_message.packing = SZ_RAWBYTES; net_message.cursize = 0; MSG_BeginReading(net_message.prim); @@ -3814,6 +3821,134 @@ void CLNQ_ConnectionlessPacket(void) void CL_MVDUpdateSpectator (void); void CL_WriteDemoMessage (sizebuf_t *msg, int payloadoffset); + +void CL_ReadPacket(void) +{ + if (!qrenderer) + return; + +#ifdef HAVE_DTLS + if (*(int *)net_message.data != -1) + if (NET_DTLS_Decode(cls.sockets)) + if (!net_message.cursize) + return; +#endif + +#ifdef NQPROT + if (cls.demoplayback == DPB_NETQUAKE) + { + MSG_BeginReading (cls.netchan.netprim); + cls.netchan.last_received = realtime; + CLNQ_ParseServerMessage (); + + if (!cls.demoplayback) + CL_NextDemo(); + return; + } +#endif +#ifdef Q2CLIENT + if (cls.demoplayback == DPB_QUAKE2) + { + MSG_BeginReading (cls.netchan.netprim); + cls.netchan.last_received = realtime; + CLQ2_ParseServerMessage (); + return; + } +#endif + // + // remote command packet + // + if (*(int *)net_message.data == -1) + { + CL_ConnectionlessPacket (); + return; + } + + if (net_message.cursize < 6 && (cls.demoplayback != DPB_MVD && cls.demoplayback != DPB_EZTV)) //MVDs don't have the whole sequence header thing going on + { + char adr[MAX_ADR_SIZE]; + Con_TPrintf ("%s: Runt packet\n", NET_AdrToString(adr, sizeof(adr), &net_from)); + return; + } + + if (cls.state == ca_disconnected) + { //connect to nq servers, but don't get confused with sequenced packets. + if (NET_WasSpecialPacket(cls.sockets)) + return; +#ifdef NQPROT + CLNQ_ConnectionlessPacket (); +#endif + return; //ignore it. We arn't connected. + } + + // + // packet from server + // + if (!cls.demoplayback && + !NET_CompareAdr (&net_from, &cls.netchan.remote_address)) + { + char adr[MAX_ADR_SIZE]; + if (NET_WasSpecialPacket(cls.sockets)) + return; + Con_DPrintf ("%s:sequenced packet from wrong server\n" + ,NET_AdrToString(adr, sizeof(adr), &net_from)); + return; + } + + if (cls.netchan.pext_stunaware) //should be safe to do this here. + if (NET_WasSpecialPacket(cls.sockets)) + return; + + switch(cls.protocol) + { + case CP_NETQUAKE: +#ifdef NQPROT + if(NQNetChan_Process(&cls.netchan)) + { + MSG_ChangePrimitives(cls.netchan.netprim); + CL_WriteDemoMessage (&net_message, msg_readcount); + CLNQ_ParseServerMessage (); + } +#endif + break; + case CP_PLUGIN: + break; + case CP_QUAKE2: +#ifdef Q2CLIENT + if (!Netchan_Process(&cls.netchan)) + return; // wasn't accepted for some reason + CLQ2_ParseServerMessage (); + break; +#endif + case CP_QUAKE3: +#ifdef Q3CLIENT + CLQ3_ParseServerMessage(); +#endif + break; + case CP_QUAKEWORLD: + if (cls.demoplayback == DPB_MVD || cls.demoplayback == DPB_EZTV) + { + MSG_BeginReading(cls.netchan.netprim); + cls.netchan.last_received = realtime; + cls.netchan.outgoing_sequence = cls.netchan.incoming_sequence; + } + else if (!Netchan_Process(&cls.netchan)) + return; // wasn't accepted for some reason + + CL_WriteDemoMessage (&net_message, msg_readcount); + + if (cls.netchan.incoming_sequence > cls.netchan.outgoing_sequence) + { //server should not be responding to packets we have not sent yet + Con_DPrintf("Server is from the future! (%i packets)\n", cls.netchan.incoming_sequence - cls.netchan.outgoing_sequence); + cls.netchan.outgoing_sequence = cls.netchan.incoming_sequence; + } + MSG_ChangePrimitives(cls.netchan.netprim); + CLQW_ParseServerMessage (); + break; + case CP_UNKNOWN: + break; + } +} /* ================= CL_ReadPackets @@ -3821,145 +3956,14 @@ CL_ReadPackets */ void CL_ReadPackets (void) { - char adr[MAX_ADR_SIZE]; - - if (!qrenderer) - return; - -// while (NET_GetPacket ()) - for(;;) + if (cls.demoplayback != DPB_NONE) { - if (!CL_GetMessage()) -#ifndef HAVE_DTLS - break; -#else - { - NET_DTLS_Timeouts(cls.sockets); - break; - } - - if (*(int *)net_message.data != -1) - if (NET_DTLS_Decode(cls.sockets)) - if (!net_message.cursize) - continue; -#endif - -#ifdef NQPROT - if (cls.demoplayback == DPB_NETQUAKE) - { - MSG_BeginReading (cls.netchan.netprim); - cls.netchan.last_received = realtime; - CLNQ_ParseServerMessage (); - - if (!cls.demoplayback) - CL_NextDemo(); - continue; - } -#endif -#ifdef Q2CLIENT - if (cls.demoplayback == DPB_QUAKE2) - { - MSG_BeginReading (cls.netchan.netprim); - cls.netchan.last_received = realtime; - CLQ2_ParseServerMessage (); - continue; - } -#endif - // - // remote command packet - // - if (*(int *)net_message.data == -1) - { - CL_ConnectionlessPacket (); - continue; - } - - if (net_message.cursize < 6 && (cls.demoplayback != DPB_MVD && cls.demoplayback != DPB_EZTV)) //MVDs don't have the whole sequence header thing going on - { - Con_TPrintf ("%s: Runt packet\n", NET_AdrToString(adr, sizeof(adr), &net_from)); - continue; - } - - if (cls.state == ca_disconnected) - { //connect to nq servers, but don't get confused with sequenced packets. - if (NET_WasSpecialPacket(cls.sockets)) - continue; -#ifdef NQPROT - CLNQ_ConnectionlessPacket (); -#endif - continue; //ignore it. We arn't connected. - } - - // - // packet from server - // - if (!cls.demoplayback && - !NET_CompareAdr (&net_from, &cls.netchan.remote_address)) - { - if (NET_WasSpecialPacket(cls.sockets)) - continue; - Con_DPrintf ("%s:sequenced packet from wrong server\n" - ,NET_AdrToString(adr, sizeof(adr), &net_from)); - continue; - } - - if (cls.netchan.pext_stunaware) //should be safe to do this here. - if (NET_WasSpecialPacket(cls.sockets)) - continue; - - switch(cls.protocol) - { - case CP_NETQUAKE: -#ifdef NQPROT - if(NQNetChan_Process(&cls.netchan)) - { - MSG_ChangePrimitives(cls.netchan.netprim); - CL_WriteDemoMessage (&net_message, msg_readcount); - CLNQ_ParseServerMessage (); - } -#endif - break; - case CP_PLUGIN: - break; - case CP_QUAKE2: -#ifdef Q2CLIENT - if (!Netchan_Process(&cls.netchan)) - continue; // wasn't accepted for some reason - CLQ2_ParseServerMessage (); - break; -#endif - case CP_QUAKE3: -#ifdef Q3CLIENT - CLQ3_ParseServerMessage(); -#endif - break; - case CP_QUAKEWORLD: - if (cls.demoplayback == DPB_MVD || cls.demoplayback == DPB_EZTV) - { - MSG_BeginReading(cls.netchan.netprim); - cls.netchan.last_received = realtime; - cls.netchan.outgoing_sequence = cls.netchan.incoming_sequence; - } - else if (!Netchan_Process(&cls.netchan)) - continue; // wasn't accepted for some reason - - CL_WriteDemoMessage (&net_message, msg_readcount); - - if (cls.netchan.incoming_sequence > cls.netchan.outgoing_sequence) - { //server should not be responding to packets we have not sent yet - Con_DPrintf("Server is from the future! (%i packets)\n", cls.netchan.incoming_sequence - cls.netchan.outgoing_sequence); - cls.netchan.outgoing_sequence = cls.netchan.incoming_sequence; - } - MSG_ChangePrimitives(cls.netchan.netprim); - CLQW_ParseServerMessage (); - break; - case CP_UNKNOWN: - break; - } - -// if (cls.demoplayback && cls.state >= ca_active && !CL_DemoBehind()) -// return; + while(CL_GetDemoMessage()) + CL_ReadPacket(); } + else + NET_ReadPackets(cls.sockets); + NET_DTLS_Timeouts(cls.sockets); // // check timeout diff --git a/engine/client/client.h b/engine/client/client.h index ed7138ce2..9123eee1b 100644 --- a/engine/client/client.h +++ b/engine/client/client.h @@ -1246,6 +1246,7 @@ void CL_ClearState (qboolean gamestart); void CLQ2_ClearState(void); void CL_ReadPackets (void); +void CL_ReadPacket(void); int CL_ReadFromServer (void); void CL_WriteToServer (usercmd_t *cmd); @@ -1273,7 +1274,7 @@ int CL_RemoveClientCommands(char *command); // cl_demo.c // void CL_StopPlayback (void); -qboolean CL_GetMessage (void); +qboolean CL_GetDemoMessage (void); void CL_WriteDemoCmd (usercmd_t *pcmd); void CL_Demo_ClientCommand(char *commandtext); //for QTV. diff --git a/engine/client/image.c b/engine/client/image.c index 80cc8ae16..f7861df1a 100644 --- a/engine/client/image.c +++ b/engine/client/image.c @@ -2,8 +2,6 @@ #include "shader.h" #include "glquake.h" //we need some of the gl format enums -//#define PURGEIMAGES //somewhat experimental still. we're still flushing more than we should. - #if defined(NPFTE) || defined(IMGTOOL) //#define Con_Printf(f, ...) //hope you're on a littleendian machine @@ -29,6 +27,14 @@ cvar_t r_dodgytgafiles = CVARD("r_dodgytgafiles", "0", "Many old glquake engines cvar_t r_dodgypcxfiles = CVARD("r_dodgypcxfiles", "0", "When enabled, this will ignore the palette stored within pcx files, for compatibility with quake2."); #endif cvar_t r_dodgymiptex = CVARD("r_dodgymiptex", "1", "When enabled, this will force regeneration of mipmaps, discarding mips1-4 like glquake did. This may eg solve fullbright issues with some maps, but may reduce distant detail levels."); +static void QDECL R_Image_BuggyCvar (struct cvar_s *var, char *oldvalue) +{ //force these cvars to value 1 if they're empty. + //cvars using this should be changed to 0 by default, once our engine bugs are debugged/fixed. + if (!*var->string) + var->ival = true; +} +cvar_t r_keepimages = CVARCD("r_keepimages", "", R_Image_BuggyCvar, "Retain unused images in memory for slightly faster map loading. FIXME: a setting of 0 may be crashy! (empty is treated as 1 for now)"); +cvar_t r_ignoremapprefixes = CVARCD("r_ignoremapprefixes", "", R_Image_BuggyCvar, "Ignores when textures were loaded from map-specific paths. FIXME: empty is currently interpretted as 1 because the alternative is too memory hungary with r_keepimages 1."); char *r_defaultimageextensions = #ifdef IMAGEFMT_DDS @@ -13181,9 +13187,7 @@ image_t *Image_FindTexture(const char *identifier, const char *subdir, unsigned { if (!((tex->flags ^ flags) & (IF_CLAMP|IF_PALETTIZE|IF_PREMULTIPLYALPHA))) { -#ifdef PURGEIMAGES - if (!strcmp(subdir, tex->subpath?tex->subpath:"")) -#endif + if (r_ignoremapprefixes.ival || !strcmp(subdir, tex->subpath?tex->subpath:"")) { tex->regsequence = r_regsequence; return tex; @@ -13423,7 +13427,7 @@ void Image_Upload (texid_t tex, uploadfmt_t fmt, void *data, void *palette, in size_t i; //skip if we're not actually changing the data/size/format. - if (!data && tex->format == fmt && tex->width == width && tex->height == height && tex->depth == 1) + if (!data && tex->format == fmt && tex->width == width && tex->height == height && tex->depth == 1 && tex->status == TEX_LOADED) return; mips.extrafree = NULL; @@ -13562,10 +13566,9 @@ void Image_DestroyTexture(image_t *tex) void Shader_TouchTextures(void); void Image_Purge(void) { -#ifdef PURGEIMAGES - image_t *tex, *a; - int loaded = 0, total = 0; - size_t mem = 0; + image_t *tex; + if (r_keepimages.ival) + return; Shader_TouchTextures(); for (tex = imagelist; tex; tex = tex->next) { @@ -13574,7 +13577,6 @@ void Image_Purge(void) if (tex->regsequence != r_regsequence) Image_UnloadTexture(tex); } -#endif } @@ -13638,7 +13640,10 @@ void Image_List_f(void) imgmem = blockbytes * (tex->width+blockwidth-1)/blockwidth * (tex->height+blockheight-1)/blockheight; if (!(tex->flags & IF_NOMIPMAP)) imgmem += imgmem/3; //mips take about a third extra mem. - Con_Printf("^2loaded (%i*%i ^4%s^2, %3fKB->%3fKB)\n", tex->width, tex->height, Image_FormatName(tex->format), loc.len/(1024.0), imgmem/(1024.0)); + if (tex->depth != 1) + Con_Printf("^2loaded (%i*%i*%i ^4%s^2, %3fKB->%3fKB)\n", tex->width, tex->height, tex->depth, Image_FormatName(tex->format), loc.len/(1024.0), imgmem/(1024.0)); + else + Con_Printf("^2loaded (%i*%i ^4%s^2, %3fKB->%3fKB)\n", tex->width, tex->height, Image_FormatName(tex->format), loc.len/(1024.0), imgmem/(1024.0)); if (tex->aliasof) { aliasedmem += imgmem; diff --git a/engine/client/net_master.c b/engine/client/net_master.c index 5d99d2092..8a6c23258 100644 --- a/engine/client/net_master.c +++ b/engine/client/net_master.c @@ -167,7 +167,7 @@ static net_masterlist_t net_masterlist[] = { // {MP_QUAKEWORLD, CVARFC("net_qwmasterextraHistoric", "master.teamdamage.com:27000", CVAR_NOSAVE, Net_Masterlist_Callback), "master.teamdamage.com"}, //Total conversions will need to define their own in defaults.cfg or whatever. - {MP_DPMASTER, CVARFC("net_masterextra1", "frag-net.com:27950 198.58.111.37:27950", CVAR_NOSAVE, Net_Masterlist_Callback)}, //admin: Eukara + {MP_DPMASTER, CVARFC("net_masterextra1", "master.frag-net.com:27950 198.58.111.37:27950", CVAR_NOSAVE, Net_Masterlist_Callback)}, //admin: Eukara // {MP_DPMASTER, CVARFC("net_masterextra1", ""/*"ghdigital.com:27950 207.55.114.154:27950"*/, CVAR_NOSAVE, Net_Masterlist_Callback)}, //(was 69.59.212.88) admin: LordHavoc {MP_DPMASTER, CVARFC("net_masterextra2", "dpmaster.deathmask.net:27950 107.161.23.68:27950 [2604:180::4ac:98c1]:27950", CVAR_NOSAVE, Net_Masterlist_Callback)}, //admin: Willis {MP_DPMASTER, CVARFC("net_masterextra3", "dpmaster.tchr.no:27950 92.62.40.73:27950", CVAR_NOSAVE, Net_Masterlist_Callback)}, //admin: tChr @@ -2724,7 +2724,7 @@ void MasterInfo_Refresh(qboolean doreset) url = va("http://%s/raw/%s", fs_manifest->rtcbroker+6, com_token); else url = va("http://%s/raw/%s", fs_manifest->rtcbroker, com_token); - Master_AddMasterHTTP(url, MT_MASTERHTTP, MP_QUAKEWORLD, "Public Servers Potentially Behind A NAT."); + Master_AddMasterHTTP(url, MT_MASTERHTTP, MP_DPMASTER, "Public Servers Potentially Behind A NAT."); } for (i = 0; net_masterlist[i].cv.name; i++) diff --git a/engine/client/renderer.c b/engine/client/renderer.c index 9ad1c6454..5a42b78a5 100644 --- a/engine/client/renderer.c +++ b/engine/client/renderer.c @@ -327,6 +327,8 @@ cvar_t r_stereo_method = CVARFD("r_stereo_method", "0", CVAR_ARCHIVE, "Valu extern cvar_t r_dodgytgafiles; extern cvar_t r_dodgypcxfiles; +extern cvar_t r_keepimages; +extern cvar_t r_ignoremapprefixes; extern cvar_t r_dodgymiptex; extern char *r_defaultimageextensions; extern cvar_t r_imageextensions; @@ -865,6 +867,8 @@ void Renderer_Init(void) Cmd_AddCommand("sky", R_ForceSky_f); //QS compat Cmd_AddCommand("loadsky", R_ForceSky_f);//DP compat + Cvar_Register(&r_keepimages, GRAPHICALNICETIES); + Cvar_Register(&r_ignoremapprefixes, GRAPHICALNICETIES); #ifdef IMAGEFMT_TGA Cvar_Register(&r_dodgytgafiles, "Hacky bug workarounds"); #endif diff --git a/engine/client/snd_al.c b/engine/client/snd_al.c index dc2e08aa9..403318ab3 100644 --- a/engine/client/snd_al.c +++ b/engine/client/snd_al.c @@ -885,7 +885,7 @@ static void OpenAL_ChannelUpdate(soundcardinfo_t *sc, channel_t *chan, chanupdat else //we don't want to play anything more. break; if (!queuedbufs) - { //queue 0.1 secs if we're starting/resetting a new stream this is to try to cover up discintinuities caused by packetloss or whatever + { //queue 0.1 secs if we're starting/resetting a new stream this is to try to cover up discontinuities caused by packetloss or whatever sfxcache_t silence; silence.speed = snd_speed; silence.width = 2; diff --git a/engine/common/com_mesh.c b/engine/common/com_mesh.c index 9e1b2f08e..8ade4969e 100644 --- a/engine/common/com_mesh.c +++ b/engine/common/com_mesh.c @@ -3447,7 +3447,6 @@ static void *Q1MDL_LoadFrameGroup (galiasinfo_t *galias, dmdl_t *pq1inmodel, mod else { Q1MDL_LoadPose(galias, pq1inmodel, verts, normals, svec, tvec, pinframe, seamremaps, mdltype, bbox); - pinframe += pq1inmodel->numverts; #ifdef _DEBUG if ((bbox[3] > frameinfo->bboxmax.v[0] || bbox[4] > frameinfo->bboxmax.v[1] || bbox[5] > frameinfo->bboxmax.v[2] || @@ -3461,6 +3460,7 @@ static void *Q1MDL_LoadFrameGroup (galiasinfo_t *galias, dmdl_t *pq1inmodel, mod Con_DPrintf(CON_WARNING"%s has incorrect frame bounds\n", loadmodel->name); galias->warned = true; } + pinframe += pq1inmodel->numverts; } #ifndef SERVERONLY diff --git a/engine/common/common.h b/engine/common/common.h index ebd1a5e2f..42009a529 100644 --- a/engine/common/common.h +++ b/engine/common/common.h @@ -528,6 +528,7 @@ struct vfsfile_s; #define FSLF_DONTREFERENCE (1u<<5) //don't add any reference flags to packages #define FSLF_IGNOREPURE (1u<<6) //use only the client's package list, ignore any lists obtained from the server (including any reordering) #define FSLF_IGNORELINKS (1u<<7) //ignore any pak/pk3 symlinks. system ones may still be followed. +#define FSLF_QUIET (1u<<8) //don't spam warnings about any dodgy paths. //if loc is valid, loc->search is always filled in, the others are filled on success. //standard return value is 0 on failure, or depth on success. diff --git a/engine/common/fs.c b/engine/common/fs.c index 2f7623127..cb9b38ff7 100644 --- a/engine/common/fs.c +++ b/engine/common/fs.c @@ -193,7 +193,7 @@ int fs_hash_files; -const char *FS_GetCleanPath(const char *pattern, char *outbuf, int outlen); +static const char *FS_GetCleanPath(const char *pattern, qboolean silent, char *outbuf, int outlen); void FS_RegisterDefaultFileSystems(void); static void COM_CreatePath (char *path); ftemanifest_t *FS_ReadDefaultManifest(char *newbasedir, size_t newbasedirsize, qboolean fixedbasedir); @@ -1355,7 +1355,7 @@ int FS_FLocateFile(const char *filename, unsigned int lflags, flocation_t *loc) loc->search = NULL; loc->len = -1; - filename = FS_GetCleanPath(filename, cleanpath, sizeof(cleanpath)); + filename = FS_GetCleanPath(filename, (lflags&FSLF_QUIET), cleanpath, sizeof(cleanpath)); if (!filename) { pf = NULL; @@ -1703,7 +1703,7 @@ void FS_ReferenceControl(unsigned int refflag, unsigned int resetflags) } //outbuf might not be written into -const char *FS_GetCleanPath(const char *pattern, char *outbuf, int outlen) +static const char *FS_GetCleanPath(const char *pattern, qboolean silent, char *outbuf, int outlen) { const char *s; char *o; @@ -1870,7 +1870,7 @@ qboolean FS_NativePath(const char *fname, enum fs_relative relativeto, char *out } else { - fname = FS_GetCleanPath(fname, cleanname, sizeof(cleanname)); + fname = FS_GetCleanPath(fname, false, cleanname, sizeof(cleanname)); if (!fname) return false; } @@ -1986,7 +1986,7 @@ vfsfile_t *FS_OpenWithFriends(const char *fname, char *sysname, size_t sysnamesi int i; char cleanname[MAX_QPATH]; - fname = FS_GetCleanPath(fname, cleanname, sizeof(cleanname)); + fname = FS_GetCleanPath(fname, false, cleanname, sizeof(cleanname)); if (!fname) return NULL; @@ -2070,7 +2070,7 @@ vfsfile_t *QDECL FS_OpenVFS(const char *filename, const char *mode, enum fs_rela //blanket-bans - filename = FS_GetCleanPath(filename, cleanname, sizeof(cleanname)); + filename = FS_GetCleanPath(filename, false, cleanname, sizeof(cleanname)); if (!filename) return NULL; @@ -3230,7 +3230,6 @@ static void FS_ExtractDir(char *in, char *out, int outlen) qboolean FS_PathURLCache(const char *url, char *path, size_t pathsize) { - const char *FS_GetCleanPath(const char *pattern, char *outbuf, int outlen); char tmp[MAX_QPATH]; char *o = tmp; const char *i = url; @@ -3259,7 +3258,7 @@ qboolean FS_PathURLCache(const char *url, char *path, size_t pathsize) } *o = 0; - if (!FS_GetCleanPath(tmp, path, pathsize)) + if (!FS_GetCleanPath(tmp, false, path, pathsize)) return false; return true; @@ -6193,7 +6192,7 @@ static int QDECL FS_EnumerateFMFs(const char *fname, qofs_t fsize, time_t mtime, return true; } -//callback must call FS_Manifest_Free. +//callback must call FS_Manifest_Free or return false. int FS_EnumerateKnownGames(qboolean (*callback)(void *usr, ftemanifest_t *man), void *usr) { int i; diff --git a/engine/common/fs_vpk.c b/engine/common/fs_vpk.c index 545353feb..1d5c64eec 100644 --- a/engine/common/fs_vpk.c +++ b/engine/common/fs_vpk.c @@ -45,7 +45,7 @@ typedef struct dvpkfile_s unsigned char archiveindex[2]; unsigned char archiveoffset[4]; unsigned char archivesize[4]; - unsigned char sentinal[2];//=0xffff; + unsigned char sentinel[2];//=0xffff; } dvpkfile_t; typedef struct @@ -362,8 +362,8 @@ static unsigned int FSVPK_WalkTree(vpk_t *vpk, const char *start, const char *en start += sizeof(*file)+preloadsize; if (start > end) return 0; //truncated... - if (file->sentinal[0] != 0xff || file->sentinal[1] != 0xff) - return 0; //sentinal failure + if (file->sentinel[0] != 0xff || file->sentinel[1] != 0xff) + return 0; //sentinel failure // Con_Printf("Found file %s%s%s%s%s\n", path, *path?"/":"", name, *ext?".":"", ext); if (!vpk) files++; diff --git a/engine/common/gl_q2bsp.c b/engine/common/gl_q2bsp.c index 7c9bd97d7..c4a91fe6a 100644 --- a/engine/common/gl_q2bsp.c +++ b/engine/common/gl_q2bsp.c @@ -37,7 +37,7 @@ //#define Q3SURF_DUST 0x00040000 cvar_t q3bsp_surf_meshcollision_flag = CVARD("q3bsp_surf_meshcollision_flag", "0x80000000", "The surfaceparm flag(s) that enables q3bsp trisoup collision"); cvar_t q3bsp_surf_meshcollision_force = CVARD("q3bsp_surf_meshcollision_force", "0", "Force mesh-based collisions on all q3bsp trisoup surfaces."); -cvar_t q3bsp_mergeq3lightmaps = CVARD("q3bsp_mergedlightmaps", "16", "Specifies the maximum number of lightmaps that may be merged for performance reasons. Unfortunately this breaks tcgen on lightmap passes - if you care, set this to 1."); +cvar_t q3bsp_mergeq3lightmaps = CVARD("q3bsp_mergelightmaps", "1", "Specifies whether to merge lightmaps into atlases in order to boost performance. Unfortunately this breaks tcgen on lightmap passes - if you care, set this to 0."); cvar_t q3bsp_bihtraces = CVARFD("_q3bsp_bihtraces", "0", CVAR_RENDERERLATCH, "Uses runtime-generated bih collision culling for faster traces."); #if Q3SURF_NODRAW != TI_NODRAW @@ -3787,12 +3787,16 @@ static void CModQ3_LoadLighting (model_t *loadmodel, qbyte *mod_base, lump_t *l) qbyte *in = mod_base + l->fileofs; qbyte *out; unsigned int samples = l->filelen; - int m, s; - int mapsize = loadmodel->lightmaps.width*loadmodel->lightmaps.height*3; + int m, s, t; + int mapstride = loadmodel->lightmaps.width*3; + int mapsize = mapstride*loadmodel->lightmaps.height; int maps; + int merge; + int mergestride; extern cvar_t gl_overbright; - extern qbyte lmgamma[256]; + float scale = (1<<(2-gl_overbright.ival)); + loadmodel->lightmaps.fmt = LM_L8; //round up the samples, in case the last one is partial. @@ -3803,7 +3807,8 @@ static void CModQ3_LoadLighting (model_t *loadmodel, qbyte *mod_base, lump_t *l) gl_overbright.flags |= CVAR_RENDERERLATCH; BuildLightMapGammaTable(1, (1<<(2-gl_overbright.ival))); - loadmodel->lightmaps.merge = 0; + loadmodel->lightmaps.mergew = 0; + loadmodel->lightmaps.mergeh = 0; loadmodel->engineflags |= MDLF_NEEDOVERBRIGHT; @@ -3816,11 +3821,28 @@ static void CModQ3_LoadLighting (model_t *loadmodel, qbyte *mod_base, lump_t *l) maps /= 2; { - int limit = min(sh_config.texture2d_maxsize / loadmodel->lightmaps.height, q3bsp_mergeq3lightmaps.ival); - loadmodel->lightmaps.merge = 1; - while (loadmodel->lightmaps.merge*2 <= limit && loadmodel->lightmaps.merge < maps) - loadmodel->lightmaps.merge *= 2; + int limitw = sh_config.texture2d_maxsize / loadmodel->lightmaps.width; + int limith = sh_config.texture2d_maxsize / loadmodel->lightmaps.height; + if (!q3bsp_mergeq3lightmaps.ival) + { + limitw = 1; + limith = 1; + } + loadmodel->lightmaps.mergeh = loadmodel->lightmaps.mergew = 1; + while (loadmodel->lightmaps.mergew*loadmodel->lightmaps.mergeh < maps) + { //this could probably be smarter. + if (loadmodel->lightmaps.mergew*2 <= limitw && loadmodel->lightmaps.mergew < loadmodel->lightmaps.mergeh) + loadmodel->lightmaps.mergew *= 2; + else if (loadmodel->lightmaps.mergeh*2 <= limith) + loadmodel->lightmaps.mergeh *= 2; + else if (loadmodel->lightmaps.mergew*2 <= limitw) + loadmodel->lightmaps.mergew *= 2; + else + break; //can't expand in either direction. + } } + merge = loadmodel->lightmaps.mergew*loadmodel->lightmaps.mergeh; + mergestride = loadmodel->lightmaps.mergew*mapstride; //q3bsp itself does not support deluxemapping. //the way it works is by interleaving the data in lightmap+deluxemap pairs. @@ -3833,16 +3855,19 @@ static void CModQ3_LoadLighting (model_t *loadmodel, qbyte *mod_base, lump_t *l) //if we have deluxemapping data then we split it here. beware externals. if (loadmodel->lightmaps.deluxemapping) { - m = loadmodel->lightmaps.merge; + m = merge; while (m < maps) - m += loadmodel->lightmaps.merge; + m += merge; loadmodel->lightdata = ZG_Malloc(&loadmodel->memgroup, mapsize*m*2); loadmodel->lightdatasize = mapsize*m*2; } else { - loadmodel->lightdatasize = samples; - loadmodel->lightdata = ZG_Malloc(&loadmodel->memgroup, samples); + m = merge; + while (m < maps) + m += merge; + loadmodel->lightdata = ZG_Malloc(&loadmodel->memgroup, mapsize*m); + loadmodel->lightdatasize = mapsize*m; } if (!loadmodel->lightdata) @@ -3854,57 +3879,65 @@ static void CModQ3_LoadLighting (model_t *loadmodel, qbyte *mod_base, lump_t *l) { out = loadmodel->lightdata; //figure out which merged lightmap we're putting it into - out += (m/loadmodel->lightmaps.merge)*loadmodel->lightmaps.merge*mapsize * (loadmodel->lightmaps.deluxemapping?2:1); + out += (m/merge)*merge*mapsize * (loadmodel->lightmaps.deluxemapping?2:1); //and the submap - out += (m%loadmodel->lightmaps.merge)*mapsize; + s = m%merge; + t = s/loadmodel->lightmaps.mergew; + s = s%loadmodel->lightmaps.mergew; + out += s*mapstride; + out += t*mergestride*loadmodel->lightmaps.height; -#if 1 //q3bsp has 4-fold overbrights, so if we're not using overbrights then we basically need to scale the values up by 4 //this will require clamping, which can result in oversaturation of channels, meaning discolouration - for(s = 0; s < mapsize; ) + for (t = 0; t < loadmodel->lightmaps.height; t++) { - float scale = (1<<(2-gl_overbright.ival)); - float i; - vec3_t l; - l[0] = *in++; - l[1] = *in++; - l[2] = *in++; - VectorScale(l, scale, l); //it should be noted that this maths is wrong if you're trying to use srgb lightmaps. - i = max(l[0], max(l[1], l[2])); - if (i > 255) - VectorScale(l, 255/i, l); //clamp the brightest channel, scaling the others down to retain chromiance. - out[s++] = l[0]; - out[s++] = l[1]; - out[s++] = l[2]; + for (s = 0; s < loadmodel->lightmaps.width; s++) + { + float i; + vec3_t l; + l[0] = *in++; + l[1] = *in++; + l[2] = *in++; + VectorScale(l, scale, l); //it should be noted that this maths is wrong if you're trying to use srgb lightmaps. + i = max(l[0], max(l[1], l[2])); + if (i > 255) + VectorScale(l, 255/i, l); //clamp the brightest channel, scaling the others down to retain chromiance. + *out++ = l[0]; + *out++ = l[1]; + *out++ = l[2]; + } + out += mergestride-mapstride; } -#else - for(s = 0; s < mapsize; s++) - out[s] = lmgamma[*in++]; -#endif + if (r_lightmap_saturation.value != 1.0f) SaturateR8G8B8(out, mapsize, r_lightmap_saturation.value); if (loadmodel->lightmaps.deluxemapping) { - out+= loadmodel->lightmaps.merge*mapsize; + out -= mergestride*loadmodel->lightmaps.height; + out += merge*mapsize; //no gamma for deluxemap - for(s = 0; s < mapsize; s+=3) + for (t = 0; t < loadmodel->lightmaps.height; t++) { - *out++ = in[0]; - *out++ = in[1]; - *out++ = in[2]; - in += 3; + for (s = 0; s < loadmodel->lightmaps.width; s++) + { + *out++ = in[0]; + *out++ = in[1]; + *out++ = in[2]; + in += 3; + } + out += mergestride-mapstride; } } } - /*for (; m%loadmodel->lightmaps.merge; m++) + /*for (; m%merge; m++) { out = loadmodel->lightdata; //figure out which merged lightmap we're putting it into - out += (m/loadmodel->lightmaps.merge)*loadmodel->lightmaps.merge*mapsize * (loadmodel->lightmaps.deluxemapping?2:1); + out += (m/merge)*merge*mapsize * (loadmodel->lightmaps.deluxemapping?2:1); //and the submap - out += (m%loadmodel->lightmaps.merge)*mapsize; + out += (m%merge)*mapsize; for(s = 0; s < mapsize; s+=3) { diff --git a/engine/common/net.h b/engine/common/net.h index de7136692..4bb17e86e 100644 --- a/engine/common/net.h +++ b/engine/common/net.h @@ -139,7 +139,7 @@ void UDP_CloseSocket (int socket); void NET_Shutdown (void); qboolean NET_GetRates(struct ftenet_connections_s *collection, float *pi, float *po, float *bi, float *bo); qboolean NET_UpdateRates(struct ftenet_connections_s *collection, qboolean inbound, size_t size); //for demos to not be weird -int NET_GetPacket (struct ftenet_connections_s *col, int firstsock); +void NET_ReadPackets (struct ftenet_connections_s *collection); neterr_t NET_SendPacket (struct ftenet_connections_s *col, int length, const void *data, netadr_t *to); int NET_LocalAddressForRemote(struct ftenet_connections_s *collection, netadr_t *remote, netadr_t *local, int idx); void NET_PrintAddresses(struct ftenet_connections_s *collection); diff --git a/engine/common/net_ice.c b/engine/common/net_ice.c index fe279d626..6a8c8691d 100644 --- a/engine/common/net_ice.c +++ b/engine/common/net_ice.c @@ -17,7 +17,7 @@ typedef struct #include "zlib.h" #endif #ifdef SUPPORT_ICE -cvar_t net_ice_exchangeprivateips = CVARD("net_ice_exchangeprivateips", "", "Boolean. When set to 0, hides private IP addresses from your peers. Only addresses determined from the other side of your router will be shared. Setting it to 0 may be desirable but it can cause connections to fail when your router does not support hairpinning, whereas 1 fixes that at the cost of exposing private IP addresses."); +cvar_t net_ice_exchangeprivateips = CVARFD("net_ice_exchangeprivateips", "", CVAR_NOTFROMSERVER, "Boolean. When set to 0, hides private IP addresses from your peers. Only addresses determined from the other side of your router will be shared. Setting it to 0 may be desirable but it can cause connections to fail when your router does not support hairpinning, whereas 1 fixes that at the cost of exposing private IP addresses."); /* Interactive Connectivity Establishment (rfc 5245) find out your peer's potential ports. @@ -280,14 +280,20 @@ static struct icestate_s *QDECL ICE_Create(void *module, const char *conname, co case ICEP_VIDEO: collection = cls.sockets; if (!collection) + { NET_InitClient(false); + collection = cls.sockets; + } break; #endif #ifndef SERVERONLY case ICEP_QWCLIENT: collection = cls.sockets; if (!collection) + { NET_InitClient(false); + collection = cls.sockets; + } break; #endif #ifndef CLIENTONLY @@ -296,6 +302,8 @@ static struct icestate_s *QDECL ICE_Create(void *module, const char *conname, co break; #endif } + if (!collection) + return NULL; //not initable or something if (!conname) { @@ -319,13 +327,6 @@ static struct icestate_s *QDECL ICE_Create(void *module, const char *conname, co con->mode = mode; - if (!collection) - { - con->connections = collection = FTENET_CreateCollection(true); - FTENET_AddToCollection(collection, "UDP", "0", NA_IP, NP_DGRAM); - FTENET_AddToCollection(collection, "natpmp", "natpmp://5351", NA_IP, NP_NATPMP); - } - con->next = icelist; icelist = con; @@ -1855,6 +1856,7 @@ static void FTENET_ICE_Heartbeat(ftenet_ice_connection_t *b) Info_SetValueForKey(info, "hostname", hostname.string, sizeof(info)); Info_SetValueForKey(info, "modname", FS_GetGamedir(true), sizeof(info)); Info_SetValueForKey(info, "mapname", InfoBuf_ValueForKey(&svs.info, "map"), sizeof(info)); + Info_SetValueForKey(info, "needpass", InfoBuf_ValueForKey(&svs.info, "needpass"), sizeof(info)); FTENET_ICE_SplurgeCmd(b, ICEMSG_SERVERINFO, -1, info); } @@ -1904,7 +1906,7 @@ static qboolean FTENET_ICE_GetPacket(ftenet_generic_connection_t *gcon) if (b->timeout > realtime) return false; b->generic.thesocket = TCP_OpenStream(&b->brokeradr); //save this for select. - b->broker = FS_OpenTCPSocket(b->generic.thesocket, true, b->brokername); + b->broker = FS_WrapTCPSocket(b->generic.thesocket, true, b->brokername); #ifdef HAVE_SSL //convert to tls... @@ -2188,7 +2190,7 @@ static qboolean FTENET_ICE_ChangeLocalAddress(struct ftenet_generic_connection_s return true; } -ftenet_generic_connection_t *FTENET_ICE_EstablishConnection(qboolean isserver, const char *address, netadr_t adr) +ftenet_generic_connection_t *FTENET_ICE_EstablishConnection(ftenet_connections_t *col, const char *address, netadr_t adr) { ftenet_ice_connection_t *newcon; const char *path; @@ -2246,7 +2248,7 @@ ftenet_generic_connection_t *FTENET_ICE_EstablishConnection(qboolean isserver, c newcon->generic.GetLocalAddresses = FTENET_ICE_GetLocalAddresses; newcon->generic.ChangeLocalAddress = FTENET_ICE_ChangeLocalAddress; - newcon->generic.islisten = isserver; + newcon->generic.islisten = col->islisten; return &newcon->generic; } diff --git a/engine/common/net_ssl_gnutls.c b/engine/common/net_ssl_gnutls.c index ee2cf214d..42abdca57 100644 --- a/engine/common/net_ssl_gnutls.c +++ b/engine/common/net_ssl_gnutls.c @@ -442,15 +442,16 @@ static void SSL_Close(vfsfile_t *vfs) qgnutls_deinit(file->session); file->session = NULL; } +} +static qboolean QDECL SSL_CloseFile(vfsfile_t *vfs) +{ + gnutlsfile_t *file = (void*)vfs; + SSL_Close(vfs); if (file->stream) { VFS_CLOSE(file->stream); file->stream = NULL; } -} -static qboolean QDECL SSL_CloseFile(vfsfile_t *vfs) -{ - SSL_Close(vfs); Z_Free(vfs); return true; } @@ -617,14 +618,15 @@ static int QDECL SSL_CheckCert(gnutls_session_t session) } //return 1 to read data. -//-1 or 0 for error or not ready +//-1 for error +//0 for not ready static int SSL_DoHandshake(gnutlsfile_t *file) { int err; //session was previously closed = error if (!file->session) { - Sys_Printf("null session\n"); + //Sys_Printf("null session\n"); return -1; } @@ -710,7 +712,7 @@ static int QDECL SSL_Write(struct vfsfile_s *f, const void *buffer, int bytestow return 0; else { - Con_Printf("TLS Send Warning %i (%i bytes)\n", written, bytestowrite); + Con_DPrintf("TLS Send Error %i (%i bytes)\n", written, bytestowrite); return -1; } } @@ -740,14 +742,11 @@ static ssize_t SSL_Push(gnutls_transport_ptr_t p, const void *data, size_t size) gnutlsfile_t *file = p; // Sys_Printf("SSL_Push: %u\n", size); int done = VFS_WRITE(file->stream, data, size); - if (!done) + if (done <= 0) { - qgnutls_transport_set_errno(file->session, EAGAIN); + qgnutls_transport_set_errno(file->session, (done==0)?EAGAIN:ECONNRESET); return -1; } - qgnutls_transport_set_errno(file->session, done<0?errno:0); -// if (done < 0) -// return 0; return done; } static ssize_t SSL_Pull(gnutls_transport_ptr_t p, void *data, size_t size) @@ -755,16 +754,12 @@ static ssize_t SSL_Pull(gnutls_transport_ptr_t p, void *data, size_t size) gnutlsfile_t *file = p; // Sys_Printf("SSL_Pull: %u\n", size); int done = VFS_READ(file->stream, data, size); - if (!done) + if (done <= 0) { - qgnutls_transport_set_errno(file->session, EAGAIN); + //use ECONNRESET instead of returning eof. + qgnutls_transport_set_errno(file->session, (done==0)?EAGAIN:ECONNRESET); return -1; } - qgnutls_transport_set_errno(file->session, done<0?errno:0); -// if (done < 0) -// { -// return 0; -// } return done; } diff --git a/engine/common/net_wins.c b/engine/common/net_wins.c index 9a49574ce..3c822c3d5 100644 --- a/engine/common/net_wins.c +++ b/engine/common/net_wins.c @@ -105,6 +105,10 @@ FTE_ALIGN(4) qbyte net_message_buffer[MAX_OVERALLMSGLEN]; // #undef HAVE_HTTPSV //#endif +#ifdef HAVE_EPOLL +static int epoll_fd = -1; +#endif + void NET_GetLocalAddress (int socket, netadr_t *out); //int TCP_OpenListenSocket (const char *localip, int port); #ifdef HAVE_IPV6 @@ -2284,7 +2288,7 @@ int TLS_GetChannelBinding(vfsfile_t *stream, qbyte *data, size_t *datasize) #if defined(HAVE_SERVER) && defined(HAVE_CLIENT) -qboolean NET_GetLoopPacket (int sock, netadr_t *from, sizebuf_t *message) +static qboolean NET_GetLoopPacket (int sock, netadr_t *from, sizebuf_t *message) { int i; loopback_t *loop; @@ -2322,7 +2326,7 @@ qboolean NET_GetLoopPacket (int sock, netadr_t *from, sizebuf_t *message) } -neterr_t NET_SendLoopPacket (int sock, int length, const void *data, netadr_t *to) +static neterr_t NET_SendLoopPacket (int sock, int length, const void *data, netadr_t *to) { int i; loopback_t *loop; @@ -2352,7 +2356,7 @@ neterr_t NET_SendLoopPacket (int sock, int length, const void *data, netadr_t *t return NETERR_SENT; } -int FTENET_Loop_GetLocalAddresses(struct ftenet_generic_connection_s *con, unsigned int *adrflags, netadr_t *addresses, const char **adrparams, int maxaddresses) +static int FTENET_Loop_GetLocalAddresses(struct ftenet_generic_connection_s *con, unsigned int *adrflags, netadr_t *addresses, const char **adrparams, int maxaddresses) { if (maxaddresses) { @@ -2365,20 +2369,20 @@ int FTENET_Loop_GetLocalAddresses(struct ftenet_generic_connection_s *con, unsig return 0; } -qboolean FTENET_Loop_GetPacket(ftenet_generic_connection_t *con) +static qboolean FTENET_Loop_GetPacket(ftenet_generic_connection_t *con) { return NET_GetLoopPacket(con->thesocket, &net_from, &net_message); } -#ifdef HAVE_PACKET +#if defined(HAVE_PACKET) && !defined(HAVE_EPOLL) //just a null function so we don't pass bad things to select. -int FTENET_Loop_SetFDSets(ftenet_generic_connection_t *gcon, fd_set *readfdset, fd_set *writefdset) +static int FTENET_Loop_SetFDSets(ftenet_generic_connection_t *gcon, fd_set *readfdset, fd_set *writefdset) { return 0; } #endif -neterr_t FTENET_Loop_SendPacket(ftenet_generic_connection_t *con, int length, const void *data, netadr_t *to) +static neterr_t FTENET_Loop_SendPacket(ftenet_generic_connection_t *con, int length, const void *data, netadr_t *to) { if (to->type == NA_LOOPBACK) { @@ -2388,7 +2392,7 @@ neterr_t FTENET_Loop_SendPacket(ftenet_generic_connection_t *con, int length, co return NETERR_NOROUTE; } -void FTENET_Loop_Close(ftenet_generic_connection_t *con) +static void FTENET_Loop_Close(ftenet_generic_connection_t *con) { int i; int sock = con->thesocket; @@ -2405,7 +2409,7 @@ void FTENET_Loop_Close(ftenet_generic_connection_t *con) Z_Free(con); } -static ftenet_generic_connection_t *FTENET_Loop_EstablishConnection(qboolean isserver, const char *address, netadr_t adr) +static ftenet_generic_connection_t *FTENET_Loop_EstablishConnection(ftenet_connections_t *col, const char *address, netadr_t adr) { ftenet_generic_connection_t *newcon; int sock; @@ -2424,11 +2428,11 @@ static ftenet_generic_connection_t *FTENET_Loop_EstablishConnection(qboolean iss newcon->GetPacket = FTENET_Loop_GetPacket; newcon->SendPacket = FTENET_Loop_SendPacket; newcon->Close = FTENET_Loop_Close; -#ifdef HAVE_PACKET +#if defined(HAVE_PACKET) && !defined(HAVE_EPOLL) newcon->SetFDSets = FTENET_Loop_SetFDSets; #endif - newcon->islisten = isserver; + newcon->islisten = col->islisten; newcon->addrtype[0] = NA_LOOPBACK; newcon->addrtype[1] = NA_INVALID; @@ -2439,30 +2443,31 @@ static ftenet_generic_connection_t *FTENET_Loop_EstablishConnection(qboolean iss #endif //============================================================================= -ftenet_connections_t *FTENET_CreateCollection(qboolean listen) +ftenet_connections_t *FTENET_CreateCollection(qboolean listen, void(*ReadPacket)(void)) { ftenet_connections_t *col; col = Z_Malloc(sizeof(*col)); col->islisten = listen; + col->ReadGamePacket = ReadPacket; return col; } #if defined(HAVE_CLIENT) && defined(HAVE_SERVER) -static ftenet_generic_connection_t *FTENET_Loop_EstablishConnection(qboolean isserver, const char *address, netadr_t adr); +static ftenet_generic_connection_t *FTENET_Loop_EstablishConnection(ftenet_connections_t *col, const char *address, netadr_t adr); #endif #ifdef HAVE_PACKET -static ftenet_generic_connection_t *FTENET_Datagram_EstablishConnection(qboolean isserver, const char *address, netadr_t adr); +static ftenet_generic_connection_t *FTENET_Datagram_EstablishConnection(ftenet_connections_t *col, const char *address, netadr_t adr); #endif #ifdef TCPCONNECT -static ftenet_generic_connection_t *FTENET_TCPConnect_EstablishConnection(qboolean isserver, const char *address, netadr_t adr); +static ftenet_generic_connection_t *FTENET_TCP_EstablishConnection(ftenet_connections_t *col, const char *address, netadr_t adr); #endif #ifdef HAVE_WEBSOCKCL -static ftenet_generic_connection_t *FTENET_WebSocket_EstablishConnection(qboolean isserver, const char *address, netadr_t adr); +static ftenet_generic_connection_t *FTENET_WebSocket_EstablishConnection(ftenet_connections_t *col, const char *address, netadr_t adr); #endif #ifdef IRCCONNECT -static ftenet_generic_connection_t *FTENET_IRCConnect_EstablishConnection(qboolean isserver, const char *address, netadr_t adr); +static ftenet_generic_connection_t *FTENET_IRCConnect_EstablishConnection(ftenet_connections_t *col, const char *address, netadr_t adr); #endif #ifdef HAVE_NATPMP -static ftenet_generic_connection_t *FTENET_NATPMP_EstablishConnection(qboolean isserver, const char *address, netadr_t adr); +static ftenet_generic_connection_t *FTENET_NATPMP_EstablishConnection(ftenet_connections_t *col, const char *address, netadr_t adr); #endif #ifdef HAVE_NATPMP @@ -2700,7 +2705,7 @@ void FTENET_NATPMP_Close(struct ftenet_generic_connection_s *con) Z_Free(con); } //qboolean Net_OpenUDPPort(char *privateip, int privateport, char *publicip, size_t publiciplen, int *publicport); -ftenet_generic_connection_t *FTENET_NATPMP_EstablishConnection(qboolean isserver, const char *address, netadr_t pmpadr) +ftenet_generic_connection_t *FTENET_NATPMP_EstablishConnection(ftenet_connections_t *col, const char *address, netadr_t pmpadr) { pmpcon_t *pmp; @@ -2958,7 +2963,7 @@ size_t NET_GetConnectionCertificate(struct ftenet_connections_s *col, netadr_t * -static qboolean FTENET_AddToCollection_Ptr(ftenet_connections_t *col, const char *name, ftenet_generic_connection_t *(*establish)(qboolean isserver, const char *address, netadr_t adr), qboolean islisten, const char *address, netadr_t *adr) +static qboolean FTENET_AddToCollection_Ptr(ftenet_connections_t *col, const char *name, ftenet_generic_connection_t *(*establish)(ftenet_connections_t *col, const char *address, netadr_t adr), const char *address, netadr_t *adr) { int count = 0; int i; @@ -2973,7 +2978,7 @@ static qboolean FTENET_AddToCollection_Ptr(ftenet_connections_t *col, const char if (col->conn[i]) if (*col->conn[i]->name && !strcmp(col->conn[i]->name, name)) { - if (adr && adr->type != NA_INVALID && islisten) + if (adr && adr->type != NA_INVALID && col->islisten) if (col->conn[i]->ChangeLocalAddress) { if (col->conn[i]->ChangeLocalAddress(col->conn[i], address, adr)) @@ -2992,9 +2997,10 @@ static qboolean FTENET_AddToCollection_Ptr(ftenet_connections_t *col, const char { if (!col->conn[i]) { - col->conn[i] = establish(islisten, address, *adr); + col->conn[i] = establish(col, address, *adr); if (!col->conn[i]) break; + col->conn[i]->connum = i+1; if (name) Q_strncpyz(col->conn[i]->name, name, sizeof(col->conn[i]->name)); count++; @@ -3006,16 +3012,14 @@ static qboolean FTENET_AddToCollection_Ptr(ftenet_connections_t *col, const char } qboolean FTENET_AddToCollection(ftenet_connections_t *col, const char *name, const char *addresslist, netadrtype_t addrtype, netproto_t addrprot) { - qboolean islisten; netadr_t adr[8]; - ftenet_generic_connection_t *(*establish[countof(adr)])(qboolean isserver, const char *address, netadr_t adr); + ftenet_generic_connection_t *(*establish[countof(adr)])(ftenet_connections_t *col, const char *address, netadr_t adr); char address[countof(adr)][256]; unsigned int i, j; qboolean success = false; if (!col) return false; - islisten = col->islisten; if (name && strchr(name, ':')) return false; @@ -3060,20 +3064,20 @@ qboolean FTENET_AddToCollection(ftenet_connections_t *col, const char *name, con #ifdef UNIXSOCKETS if (adr[i].prot == NP_DGRAM && adr[i].type == NA_UNIX) establish[i] = FTENET_Datagram_EstablishConnection; else #if defined(TCPCONNECT) - if (adr[i].prot == NP_STREAM&& adr[i].type == NA_UNIX) establish[i] = FTENET_TCPConnect_EstablishConnection; else - if (adr[i].prot == NP_WS && adr[i].type == NA_UNIX) establish[i] = FTENET_TCPConnect_EstablishConnection; else - if (adr[i].prot == NP_TLS && adr[i].type == NA_UNIX) establish[i] = FTENET_TCPConnect_EstablishConnection; else + if (adr[i].prot == NP_STREAM&& adr[i].type == NA_UNIX) establish[i] = FTENET_TCP_EstablishConnection; else + if (adr[i].prot == NP_WS && adr[i].type == NA_UNIX) establish[i] = FTENET_TCP_EstablishConnection; else + if (adr[i].prot == NP_TLS && adr[i].type == NA_UNIX) establish[i] = FTENET_TCP_EstablishConnection; else #endif #endif #if defined(TCPCONNECT) && defined(HAVE_IPV4) - if (adr[i].prot == NP_WS && adr[i].type == NA_IP) establish[i] = FTENET_TCPConnect_EstablishConnection; else - if (adr[i].prot == NP_STREAM&& adr[i].type == NA_IP) establish[i] = FTENET_TCPConnect_EstablishConnection; else - if (adr[i].prot == NP_TLS && adr[i].type == NA_IP) establish[i] = FTENET_TCPConnect_EstablishConnection; else + if (adr[i].prot == NP_WS && adr[i].type == NA_IP) establish[i] = FTENET_TCP_EstablishConnection; else + if (adr[i].prot == NP_STREAM&& adr[i].type == NA_IP) establish[i] = FTENET_TCP_EstablishConnection; else + if (adr[i].prot == NP_TLS && adr[i].type == NA_IP) establish[i] = FTENET_TCP_EstablishConnection; else #endif #if defined(TCPCONNECT) && defined(HAVE_IPV6) - if (adr[i].prot == NP_WS && adr[i].type == NA_IPV6) establish[i] = FTENET_TCPConnect_EstablishConnection; else - if (adr[i].prot == NP_STREAM&& adr[i].type == NA_IPV6) establish[i] = FTENET_TCPConnect_EstablishConnection; else - if (adr[i].prot == NP_TLS && adr[i].type == NA_IPV6) establish[i] = FTENET_TCPConnect_EstablishConnection; else + if (adr[i].prot == NP_WS && adr[i].type == NA_IPV6) establish[i] = FTENET_TCP_EstablishConnection; else + if (adr[i].prot == NP_STREAM&& adr[i].type == NA_IPV6) establish[i] = FTENET_TCP_EstablishConnection; else + if (adr[i].prot == NP_TLS && adr[i].type == NA_IPV6) establish[i] = FTENET_TCP_EstablishConnection; else #endif #ifdef SUPPORT_ICE if (adr[i].prot == NP_RTC_TCP) establish[i] = FTENET_ICE_EstablishConnection; else @@ -3087,19 +3091,19 @@ qboolean FTENET_AddToCollection(ftenet_connections_t *col, const char *name, con if (i == 1) { - success |= FTENET_AddToCollection_Ptr(col, name, establish[0], islisten, address[0], &adr[0]); + success |= FTENET_AddToCollection_Ptr(col, name, establish[0], address[0], &adr[0]); i = 0; } else - success |= FTENET_AddToCollection_Ptr(col, name, NULL, islisten, NULL, NULL); + success |= FTENET_AddToCollection_Ptr(col, name, NULL, NULL, NULL); for (j = 0; j < i; j++) { - success |= FTENET_AddToCollection_Ptr(col, va("%s:%i", name, j), establish[j], islisten, address[j], &adr[j]); + success |= FTENET_AddToCollection_Ptr(col, va("%s:%i", name, j), establish[j], address[j], &adr[j]); } for (; j < countof(adr); j++) { - success |= FTENET_AddToCollection_Ptr(col, va("%s:%i", name, j), NULL, islisten, NULL, NULL); + success |= FTENET_AddToCollection_Ptr(col, va("%s:%i", name, j), NULL, NULL, NULL); } return success; } @@ -3120,15 +3124,6 @@ void FTENET_CloseCollection(ftenet_connections_t *col) Z_Free(col); } -void FTENET_Generic_Close(ftenet_generic_connection_t *con) -{ -#ifdef HAVE_PACKET - if (con->thesocket != INVALID_SOCKET) - closesocket(con->thesocket); -#endif - Z_Free(con); -} - #if defined(_WIN32) && defined(HAVE_PACKET) int FTENET_GetLocalAddress(int port, qboolean ipx, qboolean ipv4, qboolean ipv6, unsigned int *adrflags, netadr_t *addresses, const char **adrparams, int maxaddresses) { @@ -3633,13 +3628,41 @@ static qboolean FTENET_Datagram_ChangeLocalAddress(struct ftenet_generic_connect } #endif -ftenet_generic_connection_t *FTENET_Datagram_EstablishConnection(qboolean isserver, const char *address, netadr_t adr) +static void FTENET_Datagram_Close(ftenet_generic_connection_t *con) +{ +#ifdef HAVE_PACKET + if (con->thesocket != INVALID_SOCKET) + { +#ifdef HAVE_EPOLL + epoll_ctl(epoll_fd, EPOLL_CTL_DEL, con->thesocket, NULL); +#endif + closesocket(con->thesocket); + } +#endif + Z_Free(con); +} + +#ifdef HAVE_EPOLL +static void FTENET_Datagram_Polled(epollctx_t *ctx, unsigned int events) +{ + ftenet_generic_connection_t *con = NULL; + con = (ftenet_generic_connection_t *)((qbyte*)ctx - ((qbyte*)&con->epoll-(qbyte*)con)); + while (FTENET_Datagram_GetPacket(con)) + { + net_from.connum = con->connum; + con->owner->ReadGamePacket(); + } +} +#endif + +ftenet_generic_connection_t *FTENET_Datagram_EstablishConnection(ftenet_connections_t *col, const char *address, netadr_t adr) { #ifndef HAVE_PACKET return NULL; #else //this is written to support either ipv4 or ipv6, depending on the remote addr. ftenet_generic_connection_t *newcon; + qboolean isserver = col->islisten; unsigned long _true = true; SOCKET newsocket = INVALID_SOCKET; @@ -3839,9 +3862,10 @@ ftenet_generic_connection_t *FTENET_Datagram_EstablishConnection(qboolean isserv newcon->GetLocalAddresses = FTENET_Generic_GetLocalAddresses; newcon->GetPacket = FTENET_Datagram_GetPacket; newcon->SendPacket = FTENET_Datagram_SendPacket; - newcon->Close = FTENET_Generic_Close; + newcon->Close = FTENET_Datagram_Close; newcon->ChangeLocalAddress = FTENET_Datagram_ChangeLocalAddress; + newcon->owner = col; newcon->islisten = isserver; if (hybrid) { @@ -3856,6 +3880,18 @@ ftenet_generic_connection_t *FTENET_Datagram_EstablishConnection(qboolean isserv newcon->thesocket = newsocket; +#ifdef HAVE_EPOLL + { + struct epoll_event event = {EPOLLIN|EPOLLET, {&newcon->epoll}}; + newcon->epoll.Polled = FTENET_Datagram_Polled; + if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, newsocket, &event) < 0) + { + int err = errno; + Con_Printf("epoll_ctl failed - errno %i\n", err); + } + } +#endif + return newcon; } else @@ -3867,14 +3903,22 @@ ftenet_generic_connection_t *FTENET_Datagram_EstablishConnection(qboolean isserv } #ifdef TCPCONNECT -typedef struct ftenet_tcpconnect_stream_s { +typedef struct ftenet_tcp_stream_s { vfsfile_t *clientstream; int inlen; int outlen; +#ifdef HAVE_EPOLL + epollctx_t epoll; //so our epoll dispatcher knows which connection/stream + struct ftenet_tcp_connection_s *con; +#endif + enum { +#if defined(HAVE_SERVER) || defined(SV_MASTER) TCPC_UNKNOWN, //waiting to see what they send us. +#endif + //TCPC_QTV, //included for completeness. qtv handles the sockets itself, we just parse initial handshake and then pass it over (as either a tcp or tls vfsfile_t) TCPC_QIZMO, //'qizmo\n' handshake, followed by packets prefixed with a 16bit packet length. #ifdef HAVE_HTTPSV TCPC_WEBSOCKETU, //utf-8 encoded data. @@ -3891,7 +3935,7 @@ typedef struct ftenet_tcpconnect_stream_s { float timeouttime; qboolean pinging; netadr_t remoteaddr; - struct ftenet_tcpconnect_stream_s *next; + struct ftenet_tcp_stream_s *next; SOCKET socketnum; //for select. not otherwise used. @@ -3914,15 +3958,15 @@ typedef struct ftenet_tcpconnect_stream_s { #endif } webrtc; #endif -} ftenet_tcpconnect_stream_t; +} ftenet_tcp_stream_t; -typedef struct { +typedef struct ftenet_tcp_connection_s { ftenet_generic_connection_t generic; qboolean tls; int active; - ftenet_tcpconnect_stream_t *tcpstreams; -} ftenet_tcpconnect_connection_t; + ftenet_tcp_stream_t *tcpstreams; +} ftenet_tcp_connection_t; void tobase64(unsigned char *out, int outlen, unsigned char *in, int inlen) { @@ -3959,7 +4003,7 @@ void tobase64(unsigned char *out, int outlen, unsigned char *in, int inlen) *out = 0; } -neterr_t FTENET_TCPConnect_WebSocket_Splurge(ftenet_tcpconnect_stream_t *st, enum websocketpackettype_e packettype, const qbyte *data, unsigned int length) +neterr_t FTENET_TCP_WebSocket_Splurge(ftenet_tcp_stream_t *st, enum websocketpackettype_e packettype, const qbyte *data, unsigned int length) { /*as a server, we don't need the mask stuff*/ unsigned short ctrl = 0x8000 | (packettype<<8); @@ -4067,7 +4111,7 @@ typedef char httparg_t[64]; #include "resource.h" #endif void SV_UserCmdMVDList_HTML (vfsfile_t *pipe); -qboolean FTENET_TCPConnect_HTTPResponse(ftenet_tcpconnect_stream_t *st, httparg_t arg[WCATTR_COUNT], qboolean allowgzip) +qboolean FTENET_TCP_HTTPResponse(ftenet_tcp_stream_t *st, httparg_t arg[WCATTR_COUNT], qboolean allowgzip) { char adr[256]; int i; @@ -4489,11 +4533,11 @@ qboolean FTENET_TCPConnect_HTTPResponse(ftenet_tcpconnect_stream_t *st, httparg_ return true; } -void FTENET_TCPConnect_WebRTCServerAssigned(ftenet_tcpconnect_stream_t *list, ftenet_tcpconnect_stream_t *client, ftenet_tcpconnect_stream_t *server) +void FTENET_TCP_WebRTCServerAssigned(ftenet_tcp_stream_t *list, ftenet_tcp_stream_t *client, ftenet_tcp_stream_t *server) { qbyte buffer[3]; int trynext = 0; - ftenet_tcpconnect_stream_t *o; + ftenet_tcp_stream_t *o; if (client->webrtc.clientnum < 0) client->webrtc.clientnum = 0; for(;;) @@ -4515,18 +4559,18 @@ void FTENET_TCPConnect_WebRTCServerAssigned(ftenet_tcpconnect_stream_t *list, ft buffer[2] = (client->webrtc.clientnum>>8)&0xff; // buffer[3] = (client->webrtc.clientnum>>16)&0xff; // buffer[4] = (client->webrtc.clientnum>>24)&0xff; - FTENET_TCPConnect_WebSocket_Splurge(server, WS_PACKETTYPE_BINARYFRAME, buffer, 3); + FTENET_TCP_WebSocket_Splurge(server, WS_PACKETTYPE_BINARYFRAME, buffer, 3); buffer[0] = ICEMSG_NEWPEER; buffer[1] = 0xff; buffer[2] = 0xff; // buffer[3] = 0xff; // buffer[4] = 0xff; - FTENET_TCPConnect_WebSocket_Splurge(client, WS_PACKETTYPE_BINARYFRAME, buffer, 3); + FTENET_TCP_WebSocket_Splurge(client, WS_PACKETTYPE_BINARYFRAME, buffer, 3); } } -qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcpconnect_connection_t *con, ftenet_tcpconnect_stream_t *st) +qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcp_connection_t *con, ftenet_tcp_stream_t *st) { char *resp; char adr[256]; @@ -4721,7 +4765,7 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcpconnect_connection_t *con, ftenet if (!headerscomplete) { - Con_Printf("http header parsing failed\n"); + Con_DPrintf("http header parsing failed\n"); return false; //the caller said it was complete! something's fucked if we're here } @@ -4761,8 +4805,32 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcpconnect_connection_t *con, ftenet //other fields will be ignored. if (!stricmp(arg[WCATTR_UPGRADE], "websocket") && (!stricmp(arg[WCATTR_CONNECTION], "Upgrade") || !stricmp(arg[WCATTR_CONNECTION], "keep-alive, Upgrade"))) { - if (!net_enable_websockets.ival && !net_enable_rtcbroker.ival) - return false; + int cltype; + //the choice of protocol affects the type of response that we give rather than anything mystic + if (!strcmp(arg[WCATTR_WSPROTO], "quake")) + cltype = TCPC_WEBSOCKETNQ; //raw nq data, all reliable, for compat with webquake + else if (!strcmp(arg[WCATTR_WSPROTO], "rtc_client")) + cltype = TCPC_WEBRTC_CLIENT;//not a real client. + else if (!strcmp(arg[WCATTR_WSPROTO], "rtc_host")) + cltype = TCPC_WEBRTC_HOST;//not a real client, but a competing server! oh noes! + else if (!strcmp(arg[WCATTR_WSPROTO], "binary")) + cltype = TCPC_WEBSOCKETB; //emscripten's networking libraries insists on 'binary', but we stopped using that a while back because its hostname->ip stuff was flawed. + else if (!strcmp(arg[WCATTR_WSPROTO], "fteqw")) + cltype = TCPC_WEBSOCKETB; //specific custom protocol name to avoid ambiguities. + else + cltype = TCPC_WEBSOCKETU; //nacl supports only utf-8 encoded data, at least at the time I implemented it. + + if (cltype == TCPC_WEBRTC_CLIENT||cltype==TCPC_WEBRTC_HOST) + { + if (!net_enable_rtcbroker.ival) + return false; + } + else //TCPC_WEBSOCKETNQ, TCPC_WEBSOCKETB, TCPC_WEBSOCKETU + { + if (!net_enable_websockets.ival) + return false; + } + if (websocketver != 13) { Con_DPrintf("Outdated websocket request for \"%s\" from \"%s\". got version %i, expected version 13\n", arg[WCATTR_URL], NET_AdrToString (adr, sizeof(adr), &st->remoteaddr), websocketver); @@ -4790,24 +4858,8 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcpconnect_connection_t *con, ftenet else st->remoteaddr.prot = NP_WS; - protoname = va("Sec-WebSocket-Protocol: %s\r\n", arg[WCATTR_WSPROTO]); - - //the choice of protocol affects the type of response that we give rather than anything mystic - if (!strcmp(arg[WCATTR_WSPROTO], "quake")) - st->clienttype = TCPC_WEBSOCKETNQ; //raw nq data, all reliable, for compat with webquake - else if (!strcmp(arg[WCATTR_WSPROTO], "rtc_client")) - st->clienttype = TCPC_WEBRTC_CLIENT;//not a real client. - else if (!strcmp(arg[WCATTR_WSPROTO], "rtc_host")) - st->clienttype = TCPC_WEBRTC_HOST;//not a real client, but a competing server! oh noes! - else if (!strcmp(arg[WCATTR_WSPROTO], "binary")) - st->clienttype = TCPC_WEBSOCKETB; //emscripten's networking libraries insists on 'binary', but we stopped using that a while back because its hostname->ip stuff was flawed. - else if (!strcmp(arg[WCATTR_WSPROTO], "fteqw")) - st->clienttype = TCPC_WEBSOCKETB; //specific custom protocol name to avoid ambiguities. - else - { - st->clienttype = TCPC_WEBSOCKETU; //nacl supports only utf-8 encoded data, at least at the time I implemented it. - protoname = ""; - } + protoname = (cltype==TCPC_WEBSOCKETU)?"":va("Sec-WebSocket-Protocol: %s\r\n", arg[WCATTR_WSPROTO]); + st->clienttype = cltype; switch(st->clienttype) { @@ -4859,11 +4911,11 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcpconnect_connection_t *con, ftenet net_message_buffer[0] = ICEMSG_NEWPEER; net_message_buffer[1] = 0xff; net_message_buffer[2] = 0xff; - FTENET_TCPConnect_WebSocket_Splurge(st, WS_PACKETTYPE_BINARYFRAME, net_message_buffer, 3); + FTENET_TCP_WebSocket_Splurge(st, WS_PACKETTYPE_BINARYFRAME, net_message_buffer, 3); } else if (st->clienttype == TCPC_WEBRTC_HOST || st->clienttype == TCPC_WEBRTC_CLIENT) { - ftenet_tcpconnect_stream_t *o; + ftenet_tcp_stream_t *o; if (st->clienttype == TCPC_WEBRTC_HOST) { //if its a server, then let it know its final resource name char *idstart = strchr(st->webrtc.resource, '/'); @@ -4891,13 +4943,13 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcpconnect_connection_t *con, ftenet net_message_buffer[1] = 0xff; net_message_buffer[2] = 0xff; strcpy(net_message_buffer+3, st->webrtc.resource); - FTENET_TCPConnect_WebSocket_Splurge(st, WS_PACKETTYPE_BINARYFRAME, net_message_buffer, strlen(net_message_buffer)); + FTENET_TCP_WebSocket_Splurge(st, WS_PACKETTYPE_BINARYFRAME, net_message_buffer, strlen(net_message_buffer)); //if we have (inactive) clients connected, assign them (and let them know that they need to start handshaking) for (o = con->tcpstreams; o; o = o->next) { if (o->clienttype == TCPC_WEBRTC_CLIENT && !strcmp(st->webrtc.resource, o->webrtc.resource)) - FTENET_TCPConnect_WebRTCServerAssigned(con->tcpstreams, o, st); + FTENET_TCP_WebRTCServerAssigned(con->tcpstreams, o, st); } #ifdef SV_MASTER @@ -4912,7 +4964,7 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcpconnect_connection_t *con, ftenet break; } //and assign it to this client - FTENET_TCPConnect_WebRTCServerAssigned(con->tcpstreams, st, o); + FTENET_TCP_WebRTCServerAssigned(con->tcpstreams, st, o); } } @@ -4925,10 +4977,12 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcpconnect_connection_t *con, ftenet net_message.packing = SZ_RAWBYTES; net_message.currentbit = 0; net_from = st->remoteaddr; + net_from.connum = con->generic.connum; MSG_WriteLong(&net_message, LongSwap(NETFLAG_CTL | (strlen(NQ_NETCHAN_GAMENAME)+7))); MSG_WriteByte(&net_message, CCREQ_CONNECT); MSG_WriteString(&net_message, NQ_NETCHAN_GAMENAME); MSG_WriteByte(&net_message, NQ_NETCHAN_VERSION); + con->generic.owner->ReadGamePacket(); } return true; } @@ -4937,7 +4991,7 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcpconnect_connection_t *con, ftenet { if (!net_enable_http.ival) return false; - return FTENET_TCPConnect_HTTPResponse(st, arg, acceptsgzip); + return FTENET_TCP_HTTPResponse(st, arg, acceptsgzip); } } #endif @@ -4952,10 +5006,10 @@ static int QDECL TLSPromoteRead (struct vfsfile_s *file, void *buffer, int bytes return bytestoread; } #endif -void FTENET_TCPConnect_PrintStatus(ftenet_generic_connection_t *gcon) +void FTENET_TCP_PrintStatus(ftenet_generic_connection_t *gcon) { - ftenet_tcpconnect_connection_t *con = (ftenet_tcpconnect_connection_t*)gcon; - ftenet_tcpconnect_stream_t *st; + ftenet_tcp_connection_t *con = (ftenet_tcp_connection_t*)gcon; + ftenet_tcp_stream_t *st; char adr[MAX_QPATH]; if (!con->tcpstreams) return; @@ -4989,23 +5043,590 @@ void FTENET_TCPConnect_PrintStatus(ftenet_generic_connection_t *gcon) } } } -qboolean FTENET_TCPConnect_GetPacket(ftenet_generic_connection_t *gcon) + +static qboolean FTENET_TCP_KillStream(ftenet_tcp_connection_t *con, ftenet_tcp_stream_t *st) +{ //some sort of error. kill the connection info (will be cleaned up later) +#ifdef HAVE_EPOLL + if (st->socketnum != INVALID_SOCKET) + epoll_ctl(epoll_fd, EPOLL_CTL_DEL, st->socketnum, NULL); +#endif + if (st->clientstream) + VFS_CLOSE(st->clientstream); + st->clientstream = NULL; + + if (st->dlfile) + VFS_CLOSE(st->dlfile); + + if (st->clienttype == TCPC_WEBRTC_CLIENT) + { //notify its server + ftenet_tcp_stream_t *o; + for (o = con->tcpstreams; o; o = o->next) + { + if (o->clienttype == TCPC_WEBRTC_HOST && !strcmp(o->webrtc.resource, st->webrtc.resource)) + { + qbyte msg[3]; + msg[0] = ICEMSG_PEERDROP; + msg[1] = (st->webrtc.clientnum>>0)&0xff; + msg[2] = (st->webrtc.clientnum>>8)&0xff; + + FTENET_TCP_WebSocket_Splurge(o, WS_PACKETTYPE_BINARYFRAME, msg, 3); + break; //should only be one. + } + } + } + else if (st->clienttype == TCPC_WEBRTC_HOST) + { //we're brokering a client+server. all messages should be unicasts between a client and its host, matched by resource. + ftenet_tcp_stream_t *o; + for (o = con->tcpstreams; o; o = o->next) + { + if (o->clienttype == TCPC_WEBRTC_CLIENT && !strcmp(o->webrtc.resource, st->webrtc.resource)) + { + qbyte msg[3]; + msg[0] = ICEMSG_PEERDROP; + msg[1] = (st->webrtc.clientnum>>0)&0xff; + msg[2] = (st->webrtc.clientnum>>8)&0xff; + + FTENET_TCP_WebSocket_Splurge(o, WS_PACKETTYPE_BINARYFRAME, msg, 3); + } + } +#ifdef SV_MASTER + SVM_RemoveBrokerGame(st->webrtc.resource); +#endif + } + + return false; +} +static void FTENET_TCP_Flush(ftenet_tcp_connection_t *con, ftenet_tcp_stream_t *st) { - ftenet_tcpconnect_connection_t *con = (ftenet_tcpconnect_connection_t*)gcon; + //write after the reads, for slightly faster pings + if (st->outlen && st->clientstream) + { /*try and flush any old outgoing data*/ + int done; + done = VFS_WRITE(st->clientstream, st->outbuffer, st->outlen); + if (done > 0) + { + memmove(st->outbuffer, st->outbuffer + done, st->outlen - done); + st->outlen -= done; + st->timeouttime = Sys_DoubleTime() + 30; + } + /*else if (done == 0) + { + Con_DPrintf ("tcp peer %s closed connection\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); + st->outlen = 0; + }*/ + } +} +//returns true if we read a game packet (should re-call in this case. +static enum{ + FTETCP_DONE, FTETCP_KILL, FTETCP_RETRY +} FTENET_TCP_ReadStream(ftenet_tcp_connection_t *con, ftenet_tcp_stream_t *st) +{ + char adr[MAX_ADR_SIZE]; + if (st->inlen < sizeof(st->inbuffer)-1) + { + int ret = VFS_READ(st->clientstream, st->inbuffer+st->inlen, sizeof(st->inbuffer)-1-st->inlen); + if (ret < 0) + { + st->outlen = 0; //don't flush, no point. + Con_DPrintf ("tcp peer %s closed connection\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); + return FTETCP_KILL; + } + st->inlen += ret; + } + + switch(st->clienttype) + { + case TCPC_UNKNOWN: + if (st->inlen < 6) + return FTETCP_DONE; + + //so TLS apparently uses a first byte that is always < 64. which is handy to know. + if (con->generic.islisten && st->remoteaddr.prot == NP_STREAM && st->clientstream && !((st->inbuffer[0] >= 'a' && st->inbuffer[0] <= 'z') || (st->inbuffer[0] >= 'A' && st->inbuffer[0] <= 'Z'))) + { +#if defined(HAVE_SSL) && (defined(HAVE_SERVER) || defined(HAVE_HTTPSV)) //if its non-ascii, then try and upgrade the connection to tls + if (net_enable_tls.ival) + { + //copy off our buffer so we can read it into the tls stream's buffer instead. + char tmpbuf[256]; + vfsfile_t *stream = st->clientstream; + int (QDECL *realread) (struct vfsfile_s *file, void *buffer, int bytestoread); + if (st->inlen > sizeof(net_message_buffer)) + return FTETCP_KILL; //would cause data loss... + realread = stream->ReadBytes; + stream->ReadBytes = TLSPromoteRead; + memcpy(net_message_buffer, st->inbuffer, st->inlen); + net_message.cursize = st->inlen; + //wrap the stream now + st->clientstream = FS_OpenSSL(NULL, st->clientstream, true); + st->remoteaddr.prot = NP_TLS; + if (st->clientstream) + { + //try and reclaim it all + st->inlen = VFS_READ(st->clientstream, st->inbuffer, sizeof(st->inbuffer)-1); + if (st->inlen < 0) + { //okay, something failed... + st->inlen = 0; + return FTETCP_KILL; + } + else + { + //make sure we actually read from the proper stream again + stream->ReadBytes = realread; + } + } + if (!st->clientstream || net_message.cursize) + return FTETCP_KILL; //failure, or it didn't read all the data that we buffered for it (error instead of forgetting it). + if (developer.ival) + Con_Printf("promoted peer to tls: %s\n", NET_AdrToString(tmpbuf, sizeof(tmpbuf), &st->remoteaddr)); + return FTETCP_RETRY; //might be a usable packet in there that we now need to make sense of. + } +#endif + return FTETCP_KILL; + } + + //check if its a qizmo connection (or rather a general qw-over-tcp connection) + if (st->inlen >= 6 && !strncmp(st->inbuffer, "qizmo\n", 6)) + { + if ( +#ifdef HAVE_SERVER + net_enable_qizmo.ival || +#endif + !con->generic.islisten) + { + memmove(st->inbuffer, st->inbuffer+6, st->inlen - (6)); + st->inlen -= 6; + st->clienttype = TCPC_QIZMO; + if (con->generic.islisten) + { + //send the qizmo handshake response. + if (VFS_WRITE(st->clientstream, "qizmo\n", 6) != 6) + return FTETCP_KILL; //unable to write for some reason. + } + return FTETCP_DONE; + } + return FTETCP_KILL; //not enabled. + } + + //check if we have some http-like protocol with a header that ends with two trailing new lines (carrage returns optional, at least here) + //(must have a full request header, meaning double-lineendings somewhere) + if (con->generic.islisten)// && !strncmp(st->inbuffer, "GET ", 4)) + { + //qtv or http request header. these terminate with a blank line. + int i = 0; + qboolean headerscomplete = false; + + for (; i < st->inlen; i++) + { + //we're at the start of a line, so if its a \r\n or a \n then its a blank line, and the headers are complete + if ((i+1 < st->inlen && st->inbuffer[i] == '\r' && st->inbuffer[i+1] == '\n') || + (i < st->inlen && st->inbuffer[i] == '\n')) + { + if (st->inbuffer[i] == '\n') + i++; + else + i+=2; + headerscomplete = true; + break; + } + + for (; i < st->inlen && st->inbuffer[i] != '\n'; i++) + ; + } + + if (headerscomplete) + { +#ifdef MVD_RECORDING + //for QTV connections, we just need the method and a blank line. our qtv parser will parse the actual headers. + if (!Q_strncasecmp(st->inbuffer, "QTV", 3)) + { //FIXME: make sure its removed from epoll and not killed prematurely + int r = net_enable_qtv.ival?SV_MVD_GotQTVRequest(st->clientstream, st->inbuffer, st->inbuffer+st->inlen, &st->qtvstate):-1; + i = st->inlen; + memmove(st->inbuffer, st->inbuffer+i, st->inlen - (i)); + st->inlen -= i; + switch(r) + { + case -1: //error + return FTETCP_KILL; + case 0: //retry + return FTETCP_DONE; + case 1: //accepted +#ifdef HAVE_EPOLL + //the tcp connection will now be handled by the dedicated qtv code rather than us. + //make sure we don't get tcp-handler wakeups from this connection. + epoll_ctl(epoll_fd, EPOLL_CTL_DEL, st->socketnum, NULL); + st->socketnum = INVALID_SOCKET; + st->epoll.Polled = NULL; +#endif + st->clientstream = NULL; //qtv code took it. + return FTETCP_KILL; + } + } + else +#endif + { +#ifdef HAVE_HTTPSV + if (FTENET_TCP_ParseHTTPRequest(con, st)) + return FTETCP_RETRY; +#else + Con_DPrintf ("Unknown TCP handshake from %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); +#endif + return FTETCP_KILL; + } + } + else + { + //they splurged too much data and we don't even know what they were + //either way we're expecting a request header in our buffer that can never be completed + if (st->inlen >= sizeof(st->inbuffer)-1) + return FTETCP_KILL; + } + } + + return FTETCP_DONE; +#ifdef HAVE_HTTPSV + case TCPC_HTTPCLIENT: + /*try and keep it flushed*/ + FTENET_TCP_Flush(con, st); + if (!st->outlen) + { + if (st->dlfile) + st->outlen = VFS_READ(st->dlfile, st->outbuffer, sizeof(st->outbuffer)); + else + st->outlen = 0; + if (st->outlen <= 0) + { + st->outlen = 0; + if (st->dlfile) + VFS_CLOSE(st->dlfile); + st->dlfile = NULL; + st->clienttype = TCPC_UNKNOWN; //wait for the next request (could potentially be a websocket connection) + Con_DPrintf ("Outgoing file transfer complete\n"); + if (st->httpstate.connection_close) + return FTETCP_KILL; + } + FTENET_TCP_Flush(con, st); + } + return FTETCP_DONE; +#endif + case TCPC_QIZMO: + if (st->inlen < 2) + return FTETCP_DONE; + + net_message.cursize = BigShort(*(short*)st->inbuffer); + if (net_message.cursize >= sizeof(net_message_buffer) ) + { + Con_TPrintf ("Warning: Oversize packet from %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); + return FTETCP_KILL; + } + if (net_message.cursize+2 > st->inlen) + { //not enough buffered to read a packet out of it. + return FTETCP_DONE; + } + + memcpy(net_message_buffer, st->inbuffer+2, net_message.cursize); + memmove(st->inbuffer, st->inbuffer+net_message.cursize+2, st->inlen - (net_message.cursize+2)); + st->inlen -= net_message.cursize+2; + + net_message.packing = SZ_RAWBYTES; + net_message.currentbit = 0; + net_from = st->remoteaddr; + net_from.connum = con->generic.connum; + + con->generic.owner->ReadGamePacket(); + return FTETCP_RETRY; +#ifdef HAVE_HTTPSV + case TCPC_WEBSOCKETU: + case TCPC_WEBSOCKETB: + case TCPC_WEBSOCKETNQ: + case TCPC_WEBRTC_HOST: + case TCPC_WEBRTC_CLIENT: + while (st->inlen >= 2) + { + unsigned short ctrl = ((unsigned char*)st->inbuffer)[0]<<8 | ((unsigned char*)st->inbuffer)[1]; + unsigned long paylen; + unsigned int payoffs = 2; + unsigned int mask = 0; +// st->inbuffer[st->inlen]=0; + if (ctrl & 0x7000) + { + Con_Printf ("%s: reserved bits set\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); + return FTETCP_KILL; + } + if ((ctrl & 0x7f) == 127) + { + quint64_t ullpaylen; + //as a payload is not allowed to be encoded as too large a type, and quakeworld never used packets larger than 1450 bytes anyway, this code isn't needed (65k is the max even without this) + if (sizeof(ullpaylen) < 8) + { + Con_Printf ("%s: payload frame too large\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); + return FTETCP_KILL; + } + else + { + if (payoffs + 8 > st->inlen) + break; + ullpaylen = + (quint64_t)((unsigned char*)st->inbuffer)[payoffs+0]<<56u | + (quint64_t)((unsigned char*)st->inbuffer)[payoffs+1]<<48u | + (quint64_t)((unsigned char*)st->inbuffer)[payoffs+2]<<40u | + (quint64_t)((unsigned char*)st->inbuffer)[payoffs+3]<<32u | + (quint64_t)((unsigned char*)st->inbuffer)[payoffs+4]<<24u | + (quint64_t)((unsigned char*)st->inbuffer)[payoffs+5]<<16u | + (quint64_t)((unsigned char*)st->inbuffer)[payoffs+6]<< 8u | + (quint64_t)((unsigned char*)st->inbuffer)[payoffs+7]<< 0u; + if (ullpaylen < 0x10000) + { + Con_Printf ("%s: payload size (%"PRIu64") encoded badly\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr), ullpaylen); + return FTETCP_KILL; + } + if (ullpaylen > 0x40000) + { + Con_Printf ("%s: payload size (%"PRIu64") is abusive\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr), ullpaylen); + return FTETCP_KILL; + } + paylen = ullpaylen; + payoffs += 8; + } + } + else if ((ctrl & 0x7f) == 126) + { + if (payoffs + 2 > st->inlen) + break; + paylen = + ((unsigned char*)st->inbuffer)[payoffs+0]<<8 | + ((unsigned char*)st->inbuffer)[payoffs+1]<<0; + if (paylen < 126) + { + Con_Printf ("%s: payload size encoded badly\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); + return FTETCP_KILL; + } + payoffs += 2; + } + else + { + paylen = ctrl & 0x7f; + } + if (ctrl & 0x80) + { + if (payoffs + 4 > st->inlen) + break; + /*this might read data that isn't set yet, but should be safe*/ + ((unsigned char*)&mask)[0] = ((unsigned char*)st->inbuffer)[payoffs+0]; + ((unsigned char*)&mask)[1] = ((unsigned char*)st->inbuffer)[payoffs+1]; + ((unsigned char*)&mask)[2] = ((unsigned char*)st->inbuffer)[payoffs+2]; + ((unsigned char*)&mask)[3] = ((unsigned char*)st->inbuffer)[payoffs+3]; + payoffs += 4; + } + /*if there isn't space, try again next time around*/ + if (payoffs + paylen > st->inlen) + { + if (payoffs + paylen >= sizeof(st->inbuffer)-1) + { + Con_TPrintf ("Warning: Oversize packet from %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); + return FTETCP_KILL; + } + break; + } + + if (mask) + { + int i; + for (i = 0; i < paylen; i++) + { + ((unsigned char*)st->inbuffer)[i + payoffs] ^= ((unsigned char*)&mask)[i&3]; + } + } + + net_message.cursize = 0; + + switch((ctrl>>8) & 0xf) + { + case WS_PACKETTYPE_CONTINUATION: /*continuation*/ + Con_Printf ("websocket continuation frame from %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); + return FTETCP_KILL; //can't handle these. + case WS_PACKETTYPE_TEXTFRAME: /*text frame*/ +// Con_Printf ("websocket text frame from %s\n", NET_AdrToString (adr, sizeof(adr), st->remoteaddr)); + { + /*text frames are pure utf-8 chars, no dodgy encodings or anything, all pre-checked... + except we're trying to send binary data. + so we need to unmask things (char 0 is encoded as 0x100 - truncate it) + */ + unsigned char *in = st->inbuffer+payoffs, *out = net_message_buffer; + int len = paylen; + while(len && out < net_message_buffer + sizeof(net_message_buffer)) + { + if ((*in & 0xe0)==0xc0 && len > 1) + { + *out = ((in[0] & 0x1f)<<6) | ((in[1] & 0x3f)<<0); + in+=2; + len -= 2; + } + else if (*in & 0x80) + { + *out = '?'; + in++; + len -= 1; + } + else + { + *out = in[0]; + in++; + len -= 1; + } + out++; + } + net_message.cursize = out - net_message_buffer; + } + break; + case WS_PACKETTYPE_BINARYFRAME: /*binary frame*/ +// Con_Printf ("websocket binary frame from %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); + net_message.cursize = paylen; + if (net_message.cursize+8 >= sizeof(net_message_buffer) ) + { + Con_TPrintf ("Warning: Oversize packet from %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); + return FTETCP_KILL; + } +#ifdef SUPPORT_RTC_ICE + if (st->clienttype == TCPC_WEBRTC_CLIENT && !*st->webrtc.resource) + { //this is a client that's connected directly to us via webrtc. + //FIXME: we don't support dtls, so browers will bitch about our sdp. + if (paylen+1 < sizeof(net_message_buffer)) + { + net_message_buffer[paylen] = 0; + memcpy(net_message_buffer, st->inbuffer+payoffs, paylen); + + if (!st->webrtc.ice) //if the ice state isn't established yet, do that now. + st->webrtc.ice = iceapi.ICE_Create(NULL, "test", "rtc://foo", ICEM_ICE, ICEP_QWSERVER); + iceapi.ICE_Set(st->webrtc.ice, "sdp", net_message_buffer); + + if (iceapi.ICE_Get(st->webrtc.ice, "sdp", net_message_buffer, sizeof(net_message_buffer))) + FTENET_TCP_WebSocket_Splurge(st, WS_PACKETTYPE_BINARYFRAME, net_message_buffer, strlen(net_message_buffer)); + } + net_message.cursize = 0; + } + else +#endif + if (st->clienttype == TCPC_WEBRTC_HOST && st->inbuffer[payoffs+0] == ICEMSG_SERVERINFO) + { +#ifdef SV_MASTER + qbyte old = st->inbuffer[payoffs+paylen]; + st->inbuffer[payoffs+paylen] = 0; //make sure its null terminated... + SVM_AddBrokerGame(st->webrtc.resource, st->inbuffer+payoffs+3); + st->inbuffer[payoffs+paylen] = old; +#endif + } + else if ((st->clienttype == TCPC_WEBRTC_CLIENT || st->clienttype == TCPC_WEBRTC_HOST) && paylen >= 3) + { //we're brokering a client+server. all messages should be unicasts between a client and its host, matched by resource. + ftenet_tcp_stream_t *o; + short clnum = (st->inbuffer[payoffs+1]<<0)|(st->inbuffer[payoffs+2]<<8); + int type = (st->clienttype != TCPC_WEBRTC_CLIENT)?TCPC_WEBRTC_CLIENT:TCPC_WEBRTC_HOST; + for (o = con->tcpstreams; o; o = o->next) + { + if (o->clienttype == type && clnum == o->webrtc.clientnum && !strcmp(o->webrtc.resource, st->webrtc.resource)) + { + st->inbuffer[payoffs+1] = (st->webrtc.clientnum>>0)&0xff; + st->inbuffer[payoffs+2] = (st->webrtc.clientnum>>8)&0xff; + FTENET_TCP_WebSocket_Splurge(o, WS_PACKETTYPE_BINARYFRAME, st->inbuffer+payoffs, paylen); + break; + } + } + if (!o) + Con_DPrintf("Unable to relay\n"); + net_message.cursize = 0; + } + else +#ifdef NQPROT + if (st->clienttype == TCPC_WEBSOCKETNQ) + { //hack in an 8-byte header + payoffs+=1; + paylen-=1; + memcpy(net_message_buffer+8, st->inbuffer+payoffs, paylen); + net_message.cursize=paylen+8; + ((int*)net_message_buffer)[0] = BigLong(NETFLAG_UNRELIABLE | net_message.cursize); + ((int*)net_message_buffer)[1] = LongSwap(++st->fakesequence); + } + else +#endif + memcpy(net_message_buffer, st->inbuffer+payoffs, paylen); + break; + case WS_PACKETTYPE_CLOSE: /*connection close*/ + Con_Printf ("websocket closure %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); + return FTETCP_KILL; + case WS_PACKETTYPE_PING: /*ping*/ +// Con_Printf ("websocket ping from %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); + if (FTENET_TCP_WebSocket_Splurge(st, WS_PACKETTYPE_PONG, st->inbuffer+payoffs, paylen) != NETERR_SENT) + return FTETCP_KILL; + break; + case WS_PACKETTYPE_PONG: /*pong*/ + st->timeouttime = Sys_DoubleTime() + 30; + st->pinging = false; +// Con_Printf ("websocket pong from %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); + break; + default: + Con_Printf ("Unsupported websocket opcode (%i) from %s\n", (ctrl>>8) & 0xf, NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); + return FTETCP_KILL; + } + + memmove(st->inbuffer, st->inbuffer+payoffs + paylen, st->inlen - (payoffs + paylen)); + st->inlen -= payoffs + paylen; + + if (net_message.cursize) + { + net_message.packing = SZ_RAWBYTES; + net_message.currentbit = 0; + net_from = st->remoteaddr; + net_from.connum = con->generic.connum; + con->generic.owner->ReadGamePacket(); + return FTETCP_RETRY; + } + } + return FTETCP_DONE; +#endif + } + return FTETCP_DONE; +} + +#ifdef HAVE_EPOLL +static void FTENET_TCP_Polled(epollctx_t *ctx, unsigned int events) +{ + ftenet_tcp_stream_t *st = NULL; + st = (ftenet_tcp_stream_t *)((qbyte*)ctx - ((qbyte*)&st->epoll-(qbyte*)st)); + for(;st->clientstream;) + { + switch(FTENET_TCP_ReadStream(st->con, st)) + { + case FTETCP_RETRY: + continue; + case FTETCP_KILL: + FTENET_TCP_KillStream(st->con, st); + return; + case FTETCP_DONE: + FTENET_TCP_Flush(st->con, st); + return; + } + } +} +#endif + +qboolean FTENET_TCP_GetPacket(ftenet_generic_connection_t *gcon) +{ + ftenet_tcp_connection_t *con = (ftenet_tcp_connection_t*)gcon; int ret; char adr[MAX_ADR_SIZE]; struct sockaddr_qstorage from; int fromlen; float timeval = Sys_DoubleTime(); - ftenet_tcpconnect_stream_t *st; + ftenet_tcp_stream_t *st; st = con->tcpstreams; - //remove any stale ones while (con->tcpstreams && con->tcpstreams->clientstream == NULL) - { + { //remove initial stale ones st = con->tcpstreams; con->tcpstreams = con->tcpstreams->next; +#ifdef HAVE_EPOLL + st->epoll.Polled = NULL; //to cause segfaults if we failed somehow. +#endif BZ_Free(st); con->active--; } @@ -5014,10 +5635,13 @@ qboolean FTENET_TCPConnect_GetPacket(ftenet_generic_connection_t *gcon) {//client receiving only via tcp while (st->next && st->next->clientstream == NULL) - { - ftenet_tcpconnect_stream_t *temp; + { //remove following stale ones + ftenet_tcp_stream_t *temp; temp = st->next; st->next = st->next->next; +#ifdef HAVE_EPOLL + temp->epoll.Polled = NULL; //to cause segfaults if we failed somehow. +#endif BZ_Free(temp); con->active--; } @@ -5032,514 +5656,27 @@ qboolean FTENET_TCPConnect_GetPacket(ftenet_generic_connection_t *gcon) st->timeouttime = timeval + 30; st->pinging = true; - FTENET_TCPConnect_WebSocket_Splurge(st, WS_PACKETTYPE_PING, "ping", 4); + FTENET_TCP_WebSocket_Splurge(st, WS_PACKETTYPE_PING, "ping", 4); } else #endif { Con_DPrintf ("tcp peer %s timed out\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); - goto closesvstream; + FTENET_TCP_KillStream(con, st); + continue; } } - if (st->outlen) - { /*try and flush any old outgoing data*/ - int done; - done = VFS_WRITE(st->clientstream, st->outbuffer, st->outlen); - if (done > 0) - { - memmove(st->outbuffer, st->outbuffer + done, st->outlen - done); - st->outlen -= done; - } - } - - ret = VFS_READ(st->clientstream, st->inbuffer+st->inlen, sizeof(st->inbuffer)-1-st->inlen); - if (ret < 0) + for(;st->clientstream;) { - Con_Printf ("tcp peer %s closed connection\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); -closesvstream: - if (st->clientstream) - VFS_CLOSE(st->clientstream); - st->clientstream = NULL; - net_message.cursize = 0; - - if (st->clienttype == TCPC_WEBRTC_CLIENT) - { //notify its server - ftenet_tcpconnect_stream_t *o; - for (o = con->tcpstreams; o; o = o->next) - { - if (o->clienttype == TCPC_WEBRTC_HOST && !strcmp(o->webrtc.resource, st->webrtc.resource)) - { - adr[0] = ICEMSG_PEERDROP; - adr[1] = (st->webrtc.clientnum>>0)&0xff; - adr[2] = (st->webrtc.clientnum>>8)&0xff; - - FTENET_TCPConnect_WebSocket_Splurge(o, WS_PACKETTYPE_BINARYFRAME, adr, 3); - break; //should only be one. - } - } - } - else if (st->clienttype == TCPC_WEBRTC_HOST) - { //we're brokering a client+server. all messages should be unicasts between a client and its host, matched by resource. - ftenet_tcpconnect_stream_t *o; - for (o = con->tcpstreams; o; o = o->next) - { - if (o->clienttype == TCPC_WEBRTC_CLIENT && !strcmp(o->webrtc.resource, st->webrtc.resource)) - { - adr[0] = ICEMSG_PEERDROP; - adr[1] = (st->webrtc.clientnum>>0)&0xff; - adr[2] = (st->webrtc.clientnum>>8)&0xff; - - FTENET_TCPConnect_WebSocket_Splurge(o, WS_PACKETTYPE_BINARYFRAME, adr, 3); - } - } -#ifdef SV_MASTER - SVM_RemoveBrokerGame(st->webrtc.resource); -#endif - } - continue; - } - st->inlen += ret; - - switch(st->clienttype) - { - case TCPC_UNKNOWN: - if (st->inlen < 6) + ret = FTENET_TCP_ReadStream(con, st); + if (ret == FTETCP_RETRY) continue; - - //so TLS apparently uses a first byte that is always < 64. which is handy to know. - if (con->generic.islisten && st->remoteaddr.prot == NP_STREAM && st->clientstream && !((st->inbuffer[0] >= 'a' && st->inbuffer[0] <= 'z') || (st->inbuffer[0] >= 'A' && st->inbuffer[0] <= 'Z'))) - { -#if defined(HAVE_SSL) && (defined(HAVE_SERVER) || defined(HAVE_HTTPSV)) //if its non-ascii, then try and upgrade the connection to tls - if (net_enable_tls.ival) - { - //copy off our buffer so we can read it into the tls stream's buffer instead. - char tmpbuf[256]; - vfsfile_t *stream = st->clientstream; - int (QDECL *realread) (struct vfsfile_s *file, void *buffer, int bytestoread); - realread = stream->ReadBytes; - stream->ReadBytes = TLSPromoteRead; - memcpy(net_message_buffer, st->inbuffer, st->inlen); - net_message.cursize = st->inlen; - //wrap the stream now - st->clientstream = FS_OpenSSL(NULL, st->clientstream, true); - st->remoteaddr.prot = NP_TLS; - if (st->clientstream) - { - //try and reclaim it all - st->inlen = VFS_READ(st->clientstream, st->inbuffer, sizeof(st->inbuffer)-1); - if (st->inlen < 0) - { //okay, something failed... - st->inlen = 0; - goto closesvstream; - } - else - { - //make sure we actually read from the proper stream again - stream->ReadBytes = realread; - } - } - if (!st->clientstream || net_message.cursize) - goto closesvstream; //something cocked up. we didn't give the tls stream all the data. - if (developer.ival) - Con_Printf("promoted peer to tls: %s\n", NET_AdrToString(tmpbuf, sizeof(tmpbuf), &st->remoteaddr)); - net_message.cursize = 0; - continue; - } -#endif - goto closesvstream; //something cocked up. we didn't give the tls stream all the data. - } - - if (!strncmp(st->inbuffer, "qizmo\n", 6)) - { - if ( -#ifdef HAVE_SERVER - net_enable_qizmo.ival || -#endif - !con->generic.islisten) - { - memmove(st->inbuffer, st->inbuffer+6, st->inlen - (6)); - st->inlen -= 6; - st->clienttype = TCPC_QIZMO; - if (con->generic.islisten) - { - //send the qizmo handshake response. - VFS_WRITE(st->clientstream, "qizmo\n", 6); - } - } - else - goto closesvstream; - }else -#ifdef HAVE_HTTPSV - if (con->generic.islisten)// && !strncmp(st->inbuffer, "GET ", 4)) - { - //qtv or http request header. these terminate with a blank line. - int i = 0; - qboolean headerscomplete = false; - - for (; i < st->inlen; i++) - { - //we're at the start of a line, so if its a \r\n or a \n then its a blank line, and the headers are complete - if ((i+1 < st->inlen && st->inbuffer[i] == '\r' && st->inbuffer[i+1] == '\n') || - (i < st->inlen && st->inbuffer[i] == '\n')) - { - if (st->inbuffer[i] == '\n') - i++; - else - i+=2; - headerscomplete = true; - break; - } - - for (; i < st->inlen && st->inbuffer[i] != '\n'; i++) - ; - } - - if (headerscomplete) - { -#ifdef MVD_RECORDING - //for QTV connections, we just need the method and a blank line. our qtv parser will parse the actual headers. - if (!Q_strncasecmp(st->inbuffer, "QTV", 3)) - { - int r = net_enable_qtv.ival?SV_MVD_GotQTVRequest(st->clientstream, st->inbuffer, st->inbuffer+st->inlen, &st->qtvstate):-1; - i = st->inlen; - memmove(st->inbuffer, st->inbuffer+i, st->inlen - (i)); - st->inlen -= i; - switch(r) - { - case -1: - goto closesvstream; - case 0: - continue; - case 1: - st->clientstream = NULL; - continue; - } - } - else -#endif - { - net_message.cursize = 0; - if (!FTENET_TCP_ParseHTTPRequest(con, st)) - goto closesvstream; - - if (net_message.cursize > 0) - return true; - } - continue; - } - }else -#endif - { - Con_DPrintf ("Unknown TCP handshake from %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); - goto closesvstream; - } - + else if (ret == FTETCP_KILL) + FTENET_TCP_KillStream(con, st); break; -#ifdef HAVE_HTTPSV - case TCPC_HTTPCLIENT: - if (st->outlen) - { /*try and flush the old data*/ - int done; - done = VFS_WRITE(st->clientstream, st->outbuffer, st->outlen); - if (done > 0) - { - memmove(st->outbuffer, st->outbuffer + done, st->outlen - done); - st->outlen -= done; - - st->timeouttime = timeval + 30; - } - } - if (!st->outlen) - { - if (st->dlfile) - st->outlen = VFS_READ(st->dlfile, st->outbuffer, sizeof(st->outbuffer)); - else - st->outlen = 0; - if (st->outlen <= 0) - { - if (st->dlfile) - VFS_CLOSE(st->dlfile); - st->dlfile = NULL; - st->clienttype = TCPC_UNKNOWN; - Con_DPrintf ("Outgoing file transfer complete\n"); - if (st->httpstate.connection_close) - goto closesvstream; - } - } - continue; -#endif - case TCPC_QIZMO: - if (st->inlen < 2) - continue; - - net_message.cursize = BigShort(*(short*)st->inbuffer); - if (net_message.cursize >= sizeof(net_message_buffer) ) - { - Con_TPrintf ("Warning: Oversize packet from %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); - goto closesvstream; - } - if (net_message.cursize+2 > st->inlen) - { //not enough buffered to read a packet out of it. - continue; - } - - memcpy(net_message_buffer, st->inbuffer+2, net_message.cursize); - memmove(st->inbuffer, st->inbuffer+net_message.cursize+2, st->inlen - (net_message.cursize+2)); - st->inlen -= net_message.cursize+2; - - net_message.packing = SZ_RAWBYTES; - net_message.currentbit = 0; - net_from = st->remoteaddr; - - return true; -#ifdef HAVE_HTTPSV - case TCPC_WEBSOCKETU: - case TCPC_WEBSOCKETB: - case TCPC_WEBSOCKETNQ: - case TCPC_WEBRTC_HOST: - case TCPC_WEBRTC_CLIENT: - while (st->inlen >= 2) - { - unsigned short ctrl = ((unsigned char*)st->inbuffer)[0]<<8 | ((unsigned char*)st->inbuffer)[1]; - unsigned long paylen; - unsigned int payoffs = 2; - unsigned int mask = 0; -// st->inbuffer[st->inlen]=0; - if (ctrl & 0x7000) - { - Con_Printf ("%s: reserved bits set\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); - goto closesvstream; - } - if ((ctrl & 0x7f) == 127) - { - quint64_t ullpaylen; - //as a payload is not allowed to be encoded as too large a type, and quakeworld never used packets larger than 1450 bytes anyway, this code isn't needed (65k is the max even without this) - if (sizeof(ullpaylen) < 8) - { - Con_Printf ("%s: payload frame too large\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); - goto closesvstream; - } - else - { - if (payoffs + 8 > st->inlen) - break; - ullpaylen = - (quint64_t)((unsigned char*)st->inbuffer)[payoffs+0]<<56u | - (quint64_t)((unsigned char*)st->inbuffer)[payoffs+1]<<48u | - (quint64_t)((unsigned char*)st->inbuffer)[payoffs+2]<<40u | - (quint64_t)((unsigned char*)st->inbuffer)[payoffs+3]<<32u | - (quint64_t)((unsigned char*)st->inbuffer)[payoffs+4]<<24u | - (quint64_t)((unsigned char*)st->inbuffer)[payoffs+5]<<16u | - (quint64_t)((unsigned char*)st->inbuffer)[payoffs+6]<< 8u | - (quint64_t)((unsigned char*)st->inbuffer)[payoffs+7]<< 0u; - if (ullpaylen < 0x10000) - { - Con_Printf ("%s: payload size (%"PRIu64") encoded badly\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr), ullpaylen); - goto closesvstream; - } - if (ullpaylen > 0x40000) - { - Con_Printf ("%s: payload size (%"PRIu64") is abusive\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr), ullpaylen); - goto closesvstream; - } - paylen = ullpaylen; - payoffs += 8; - } - } - else if ((ctrl & 0x7f) == 126) - { - if (payoffs + 2 > st->inlen) - break; - paylen = - ((unsigned char*)st->inbuffer)[payoffs+0]<<8 | - ((unsigned char*)st->inbuffer)[payoffs+1]<<0; - if (paylen < 126) - { - Con_Printf ("%s: payload size encoded badly\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); - goto closesvstream; - } - payoffs += 2; - } - else - { - paylen = ctrl & 0x7f; - } - if (ctrl & 0x80) - { - if (payoffs + 4 > st->inlen) - break; - /*this might read data that isn't set yet, but should be safe*/ - ((unsigned char*)&mask)[0] = ((unsigned char*)st->inbuffer)[payoffs+0]; - ((unsigned char*)&mask)[1] = ((unsigned char*)st->inbuffer)[payoffs+1]; - ((unsigned char*)&mask)[2] = ((unsigned char*)st->inbuffer)[payoffs+2]; - ((unsigned char*)&mask)[3] = ((unsigned char*)st->inbuffer)[payoffs+3]; - payoffs += 4; - } - /*if there isn't space, try again next time around*/ - if (payoffs + paylen > st->inlen) - { - if (payoffs + paylen >= sizeof(st->inbuffer)-1) - { - Con_TPrintf ("Warning: Oversize packet from %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); - goto closesvstream; //can't ever complete - } - break; - } - - if (mask) - { - int i; - for (i = 0; i < paylen; i++) - { - ((unsigned char*)st->inbuffer)[i + payoffs] ^= ((unsigned char*)&mask)[i&3]; - } - } - - net_message.cursize = 0; - - switch((ctrl>>8) & 0xf) - { - case WS_PACKETTYPE_CONTINUATION: /*continuation*/ - Con_Printf ("websocket continuation frame from %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); - goto closesvstream; //can't handle these. - case WS_PACKETTYPE_TEXTFRAME: /*text frame*/ -// Con_Printf ("websocket text frame from %s\n", NET_AdrToString (adr, sizeof(adr), st->remoteaddr)); - { - /*text frames are pure utf-8 chars, no dodgy encodings or anything, all pre-checked... - except we're trying to send binary data. - so we need to unmask things (char 0 is encoded as 0x100 - truncate it) - */ - unsigned char *in = st->inbuffer+payoffs, *out = net_message_buffer; - int len = paylen; - while(len && out < net_message_buffer + sizeof(net_message_buffer)) - { - if ((*in & 0xe0)==0xc0 && len > 1) - { - *out = ((in[0] & 0x1f)<<6) | ((in[1] & 0x3f)<<0); - in+=2; - len -= 2; - } - else if (*in & 0x80) - { - *out = '?'; - in++; - len -= 1; - } - else - { - *out = in[0]; - in++; - len -= 1; - } - out++; - } - net_message.cursize = out - net_message_buffer; - } - break; - case WS_PACKETTYPE_BINARYFRAME: /*binary frame*/ -// Con_Printf ("websocket binary frame from %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); - net_message.cursize = paylen; - if (net_message.cursize+8 >= sizeof(net_message_buffer) ) - { - Con_TPrintf ("Warning: Oversize packet from %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); - goto closesvstream; - } -#ifdef SUPPORT_RTC_ICE - if (st->clienttype == TCPC_WEBRTC_CLIENT && !*st->webrtc.resource) - { //this is a client that's connected directly to us via webrtc. - //FIXME: we don't support dtls, so browers will bitch about our sdp. - if (paylen+1 < sizeof(net_message_buffer)) - { - net_message_buffer[paylen] = 0; - memcpy(net_message_buffer, st->inbuffer+payoffs, paylen); - - if (!st->webrtc.ice) //if the ice state isn't established yet, do that now. - st->webrtc.ice = iceapi.ICE_Create(NULL, "test", "rtc://foo", ICEM_ICE, ICEP_QWSERVER); - iceapi.ICE_Set(st->webrtc.ice, "sdp", net_message_buffer); - - if (iceapi.ICE_Get(st->webrtc.ice, "sdp", net_message_buffer, sizeof(net_message_buffer))) - FTENET_TCPConnect_WebSocket_Splurge(st, WS_PACKETTYPE_BINARYFRAME, net_message_buffer, strlen(net_message_buffer)); - } - net_message.cursize = 0; - } - else -#endif - if (st->clienttype == TCPC_WEBRTC_HOST && st->inbuffer[payoffs+0] == ICEMSG_SERVERINFO) - { -#ifdef SV_MASTER - qbyte old = st->inbuffer[payoffs+paylen]; - st->inbuffer[payoffs+paylen] = 0; //make sure its null terminated... - SVM_AddBrokerGame(st->webrtc.resource, st->inbuffer+payoffs+3); - st->inbuffer[payoffs+paylen] = old; -#endif - } - else if ((st->clienttype == TCPC_WEBRTC_CLIENT || st->clienttype == TCPC_WEBRTC_HOST) && paylen >= 3) - { //we're brokering a client+server. all messages should be unicasts between a client and its host, matched by resource. - ftenet_tcpconnect_stream_t *o; - short clnum = (st->inbuffer[payoffs+1]<<0)|(st->inbuffer[payoffs+2]<<8); - int type = (st->clienttype != TCPC_WEBRTC_CLIENT)?TCPC_WEBRTC_CLIENT:TCPC_WEBRTC_HOST; - for (o = con->tcpstreams; o; o = o->next) - { - if (o->clienttype == type && clnum == o->webrtc.clientnum && !strcmp(o->webrtc.resource, st->webrtc.resource)) - { - st->inbuffer[payoffs+1] = (st->webrtc.clientnum>>0)&0xff; - st->inbuffer[payoffs+2] = (st->webrtc.clientnum>>8)&0xff; - FTENET_TCPConnect_WebSocket_Splurge(o, WS_PACKETTYPE_BINARYFRAME, st->inbuffer+payoffs, paylen); - break; - } - } - if (!o) - Con_DPrintf("Unable to relay\n"); - net_message.cursize = 0; - } - else -#ifdef NQPROT - if (st->clienttype == TCPC_WEBSOCKETNQ) - { //hack in an 8-byte header - payoffs+=1; - paylen-=1; - memcpy(net_message_buffer+8, st->inbuffer+payoffs, paylen); - net_message.cursize=paylen+8; - ((int*)net_message_buffer)[0] = BigLong(NETFLAG_UNRELIABLE | net_message.cursize); - ((int*)net_message_buffer)[1] = LongSwap(++st->fakesequence); - } - else -#endif - memcpy(net_message_buffer, st->inbuffer+payoffs, paylen); - break; - case WS_PACKETTYPE_CLOSE: /*connection close*/ - Con_Printf ("websocket closure %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); - goto closesvstream; - case WS_PACKETTYPE_PING: /*ping*/ -// Con_Printf ("websocket ping from %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); - if (FTENET_TCPConnect_WebSocket_Splurge(st, WS_PACKETTYPE_PONG, st->inbuffer+payoffs, paylen) != NETERR_SENT) - goto closesvstream; - break; - case WS_PACKETTYPE_PONG: /*pong*/ - st->timeouttime = Sys_DoubleTime() + 30; - st->pinging = false; -// Con_Printf ("websocket pong from %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); - break; - default: - Con_Printf ("Unsupported websocket opcode (%i) from %s\n", (ctrl>>8) & 0xf, NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); - goto closesvstream; - } - - memmove(st->inbuffer, st->inbuffer+payoffs + paylen, st->inlen - (payoffs + paylen)); - st->inlen -= payoffs + paylen; - - if (net_message.cursize) - { - net_message.packing = SZ_RAWBYTES; - net_message.currentbit = 0; - net_from = st->remoteaddr; - return true; - } - } - break; -#endif } + FTENET_TCP_Flush(con, st); } if (con->generic.thesocket != INVALID_SOCKET && con->active < 256) @@ -5564,9 +5701,18 @@ closesvstream: st->next = con->tcpstreams; con->tcpstreams = st; st->socketnum = newsock; - st->clientstream = FS_OpenTCPSocket(newsock, false, NET_AdrToString(tmpbuf, sizeof(tmpbuf), &st->remoteaddr)); + st->clientstream = FS_WrapTCPSocket(newsock, false, NET_AdrToString(tmpbuf, sizeof(tmpbuf), &st->remoteaddr)); st->inlen = 0; +#ifdef HAVE_EPOLL + { + struct epoll_event event = {EPOLLIN|EPOLLOUT|EPOLLET, {&st->epoll}}; + st->con = con; + st->epoll.Polled = FTENET_TCP_Polled; + epoll_ctl(epoll_fd, EPOLL_CTL_ADD, newsock, &event); + } +#endif + #ifdef HAVE_SSL if (con->tls && st->clientstream) //if we're meant to be using tls, wrap the stream in a tls connection { @@ -5587,10 +5733,10 @@ closesvstream: return false; } -neterr_t FTENET_TCPConnect_SendPacket(ftenet_generic_connection_t *gcon, int length, const void *data, netadr_t *to) +neterr_t FTENET_TCP_SendPacket(ftenet_generic_connection_t *gcon, int length, const void *data, netadr_t *to) { - ftenet_tcpconnect_connection_t *con = (ftenet_tcpconnect_connection_t*)gcon; - ftenet_tcpconnect_stream_t *st; + ftenet_tcp_connection_t *con = (ftenet_tcp_connection_t*)gcon; + ftenet_tcp_stream_t *st; for (st = con->tcpstreams; st; st = st->next) { @@ -5612,7 +5758,7 @@ neterr_t FTENET_TCPConnect_SendPacket(ftenet_generic_connection_t *gcon, int len { if (length+sizeof(slen) > sizeof(st->outbuffer)) return NETERR_MTU; - Con_DPrintf("FTENET_TCPConnect_SendPacket: outgoing overflow\n"); + Con_DPrintf("FTENET_TCP_SendPacket: outgoing overflow\n"); return NETERR_CLOGGED; } else @@ -5636,7 +5782,7 @@ neterr_t FTENET_TCPConnect_SendPacket(ftenet_generic_connection_t *gcon, int len case TCPC_WEBSOCKETU: case TCPC_WEBSOCKETB: { - neterr_t e = FTENET_TCPConnect_WebSocket_Splurge(st, (st->clienttype==TCPC_WEBSOCKETU)?WS_PACKETTYPE_TEXTFRAME:WS_PACKETTYPE_BINARYFRAME, data, length); + neterr_t e = FTENET_TCP_WebSocket_Splurge(st, (st->clienttype==TCPC_WEBSOCKETU)?WS_PACKETTYPE_TEXTFRAME:WS_PACKETTYPE_BINARYFRAME, data, length); if (e != NETERR_SENT) return e; } @@ -5666,9 +5812,9 @@ neterr_t FTENET_TCPConnect_SendPacket(ftenet_generic_connection_t *gcon, int len return NETERR_NOROUTE; } -static int FTENET_TCPConnect_GetLocalAddresses(struct ftenet_generic_connection_s *gcon, unsigned int *adrflags, netadr_t *addresses, const char **adrparams, int maxaddresses) +static int FTENET_TCP_GetLocalAddresses(struct ftenet_generic_connection_s *gcon, unsigned int *adrflags, netadr_t *addresses, const char **adrparams, int maxaddresses) { - ftenet_tcpconnect_connection_t *con = (ftenet_tcpconnect_connection_t*)gcon; + ftenet_tcp_connection_t *con = (ftenet_tcp_connection_t*)gcon; netproto_t prot = con->tls?NP_TLS:NP_STREAM; int i, r = FTENET_Generic_GetLocalAddresses(gcon, adrflags, addresses, adrparams, maxaddresses); for (i = 0; i < r; i++) @@ -5678,7 +5824,7 @@ static int FTENET_TCPConnect_GetLocalAddresses(struct ftenet_generic_connection_ return r; } -static qboolean FTENET_TCPConnect_ChangeLocalAddress(struct ftenet_generic_connection_s *con, const char *addressstring, netadr_t *adr) +static qboolean FTENET_TCP_ChangeLocalAddress(struct ftenet_generic_connection_s *con, const char *addressstring, netadr_t *adr) { //if we're a server, we want to try switching listening tcp port without shutting down all other connections. //yes, this might mean we leave a connection active on the old port, but oh well. @@ -5687,7 +5833,7 @@ static qboolean FTENET_TCPConnect_ChangeLocalAddress(struct ftenet_generic_conne struct sockaddr_qstorage qs; struct sockaddr_qstorage cur; netadr_t n; - SOCKET newsocket; + SOCKET newsocket = INVALID_SOCKET; unsigned long _true = true; int sysprot; @@ -5729,6 +5875,7 @@ static qboolean FTENET_TCPConnect_ChangeLocalAddress(struct ftenet_generic_conne } #if defined(HAVE_IPV6) && defined(IPV6_V6ONLY) + if (newsocket == INVALID_SOCKET) if (family == AF_INET && net_hybriddualstack.ival && !((struct sockaddr_in*)&qs)->sin_addr.s_addr) { //hybrid sockets pathway takes over when INADDR_ANY unsigned long _false = false; @@ -5749,67 +5896,88 @@ static qboolean FTENET_TCPConnect_ChangeLocalAddress(struct ftenet_generic_conne addrsize2 = NetadrToSockadr(&n, &cur); if ((bind(newsocket, (struct sockaddr *)&cur, addrsize2) != INVALID_SOCKET) && - (listen(newsocket, 2) != INVALID_SOCKET)) + (listen(newsocket, 2) != INVALID_SOCKET) && + ioctlsocket (newsocket, FIONBIO, &_true) != -1) { - if (ioctlsocket (newsocket, FIONBIO, &_true) != -1) - { - setsockopt(newsocket, IPPROTO_TCP, TCP_NODELAY, (char *)&_true, sizeof(_true)); + con->addrtype[0] = NA_IP; + con->addrtype[1] = NA_IPV6; + } + else + { + closesocket(newsocket); + newsocket = INVALID_SOCKET; + } + } + else + { + closesocket(newsocket); + newsocket = INVALID_SOCKET; + } + } + } +#endif - con->addrtype[0] = NA_IP; - con->addrtype[1] = NA_IPV6; - con->thesocket = newsocket; - return true; + if (newsocket == INVALID_SOCKET) + { + if ((newsocket = socket (family, SOCK_STREAM, sysprot)) != INVALID_SOCKET) + { +#ifdef UNIXSOCKETS + if (family == AF_UNIX) + { + struct sockaddr_un *un = (struct sockaddr_un *)&qs; + struct stat s; + if (*un->sun_path) + { //non-abstract sockets don't clean up the filesystem when the socket is closed + //and we can't re-bind to it while it still exists. + //so standard practise is to delete it before the bind. + //we do want to make sure the file is actually a socket before we remove it (so people can't abuse stuffcmds) + if (stat(un->sun_path, &s)!=-1) + { + if ((s.st_mode & S_IFMT) == S_IFSOCK) + unlink(un->sun_path); } } } - closesocket(newsocket); +#endif + + if ((bind(newsocket, (struct sockaddr *)&qs, addrsize) != INVALID_SOCKET) && + (listen(newsocket, 2) != INVALID_SOCKET) && + ioctlsocket (newsocket, FIONBIO, &_true) != -1) + ; + else + { + closesocket(newsocket); + newsocket = INVALID_SOCKET; + } } } -#endif - if ((newsocket = socket (family, SOCK_STREAM, sysprot)) != INVALID_SOCKET) + + if (newsocket != INVALID_SOCKET) { #ifdef UNIXSOCKETS - if (family == AF_UNIX) + if (family != NA_UNIX) +#endif + setsockopt(newsocket, IPPROTO_TCP, TCP_NODELAY, (char *)&_true, sizeof(_true)); + + con->thesocket = newsocket; + + +#ifdef HAVE_EPOLL { - struct sockaddr_un *un = (struct sockaddr_un *)&qs; - struct stat s; - if (*un->sun_path) - { //non-abstract sockets don't clean up the filesystem when the socket is closed - //and we can't re-bind to it while it still exists. - //so standard practise is to delete it before the bind. - //we do want to make sure the file is actually a socket before we remove it (so people can't abuse stuffcmds) - if (stat(un->sun_path, &s)!=-1) - { - if ((s.st_mode & S_IFMT) == S_IFSOCK) - unlink(un->sun_path); - } - } + struct epoll_event event = {EPOLLIN|EPOLLET, {NULL}};//&newcon->generic.epoll}}; + //newcon->generic.epoll.Polled = FTENET_TCP_AcceptPolled; + epoll_ctl(epoll_fd, EPOLL_CTL_ADD, newsocket, &event); } #endif - - if ((bind(newsocket, (struct sockaddr *)&qs, addrsize) != INVALID_SOCKET) && - (listen(newsocket, 2) != INVALID_SOCKET)) - { - if (ioctlsocket (newsocket, FIONBIO, &_true) != -1) - { -#ifdef UNIXSOCKETS - if (family != NA_UNIX) -#endif - setsockopt(newsocket, IPPROTO_TCP, TCP_NODELAY, (char *)&_true, sizeof(_true)); - - con->thesocket = newsocket; - return true; - } - } - closesocket(newsocket); + return true; } return false; } -static void FTENET_TCPConnect_Close(ftenet_generic_connection_t *gcon) +static void FTENET_TCP_Close(ftenet_generic_connection_t *gcon) { - ftenet_tcpconnect_connection_t *con = (ftenet_tcpconnect_connection_t*)gcon; - ftenet_tcpconnect_stream_t *st; + ftenet_tcp_connection_t *con = (ftenet_tcp_connection_t*)gcon; + ftenet_tcp_stream_t *st; st = con->tcpstreams; while (con->tcpstreams) @@ -5823,15 +5991,15 @@ static void FTENET_TCPConnect_Close(ftenet_generic_connection_t *gcon) BZ_Free(st); } - FTENET_Generic_Close(gcon); + FTENET_Datagram_Close(gcon); } -#ifdef HAVE_PACKET -int FTENET_TCPConnect_SetFDSets(ftenet_generic_connection_t *gcon, fd_set *readfdset, fd_set *writefdset) +#if defined(HAVE_PACKET) && !defined(HAVE_EPOLL) +static int FTENET_TCP_SetFDSets(ftenet_generic_connection_t *gcon, fd_set *readfdset, fd_set *writefdset) { - int maxfd = 0; - ftenet_tcpconnect_connection_t *con = (ftenet_tcpconnect_connection_t*)gcon; - ftenet_tcpconnect_stream_t *st; + int maxfd = -1; + ftenet_tcp_connection_t *con = (ftenet_tcp_connection_t*)gcon; + ftenet_tcp_stream_t *st; for (st = con->tcpstreams; st; st = st->next) { @@ -5840,7 +6008,7 @@ int FTENET_TCPConnect_SetFDSets(ftenet_generic_connection_t *gcon, fd_set *readf { while(iceapi.ICE_GetLCandidateSDP(st->webrtc.ice, net_message_buffer, sizeof(net_message_buffer))) { - FTENET_TCPConnect_WebSocket_Splurge(st, WS_PACKETTYPE_BINARYFRAME, net_message_buffer, strlen(net_message_buffer)); + FTENET_TCP_WebSocket_Splurge(st, WS_PACKETTYPE_BINARYFRAME, net_message_buffer, strlen(net_message_buffer)); } continue; } @@ -5848,7 +6016,7 @@ int FTENET_TCPConnect_SetFDSets(ftenet_generic_connection_t *gcon, fd_set *readf if (st->clientstream == NULL || st->socketnum == INVALID_SOCKET) continue; #ifdef HAVE_HTTPSV - if (st->clienttype == TCPC_HTTPCLIENT) + if (st->clienttype == TCPC_HTTPCLIENT && st->outlen) FD_SET(st->socketnum, writefdset); // network socket #endif FD_SET(st->socketnum, readfdset); // network socket @@ -5865,10 +6033,11 @@ int FTENET_TCPConnect_SetFDSets(ftenet_generic_connection_t *gcon, fd_set *readf } #endif -ftenet_generic_connection_t *FTENET_TCPConnect_EstablishConnection(qboolean isserver, const char *address, netadr_t adr) +ftenet_generic_connection_t *FTENET_TCP_EstablishConnection(ftenet_connections_t *col, const char *address, netadr_t adr) { //this is written to support either ipv4 or ipv6, depending on the remote addr. - ftenet_tcpconnect_connection_t *newcon; + ftenet_tcp_connection_t *newcon; + qboolean isserver = col->islisten; unsigned long _true = true; SOCKET newsocket; @@ -5883,8 +6052,7 @@ ftenet_generic_connection_t *FTENET_TCPConnect_EstablishConnection(qboolean isse #endif newcon = Z_Malloc(sizeof(*newcon)); - newcon->generic.thesocket = INVALID_SOCKET; - newsocket = INVALID_SOCKET; + newcon->generic.thesocket = newsocket = INVALID_SOCKET; newcon->generic.addrtype[0] = adr.type; newcon->generic.addrtype[1] = NA_INVALID; @@ -5892,7 +6060,7 @@ ftenet_generic_connection_t *FTENET_TCPConnect_EstablishConnection(qboolean isse if (isserver) { #ifdef HAVE_PACKET //unable to listen on tcp if we have no packet interface - if (!FTENET_TCPConnect_ChangeLocalAddress(&newcon->generic, address, &adr)) + if (!FTENET_TCP_ChangeLocalAddress(&newcon->generic, address, &adr)) { Z_Free(newcon); return NULL; @@ -5923,17 +6091,18 @@ ftenet_generic_connection_t *FTENET_TCPConnect_EstablishConnection(qboolean isse newcon->tls = tls; if (isserver) { - newcon->generic.GetLocalAddresses = FTENET_TCPConnect_GetLocalAddresses; - newcon->generic.ChangeLocalAddress = FTENET_TCPConnect_ChangeLocalAddress; + newcon->generic.GetLocalAddresses = FTENET_TCP_GetLocalAddresses; + newcon->generic.ChangeLocalAddress = FTENET_TCP_ChangeLocalAddress; } - newcon->generic.GetPacket = FTENET_TCPConnect_GetPacket; - newcon->generic.SendPacket = FTENET_TCPConnect_SendPacket; - newcon->generic.Close = FTENET_TCPConnect_Close; -#ifdef HAVE_PACKET - newcon->generic.SetFDSets = FTENET_TCPConnect_SetFDSets; + newcon->generic.GetPacket = FTENET_TCP_GetPacket; + newcon->generic.SendPacket = FTENET_TCP_SendPacket; + newcon->generic.Close = FTENET_TCP_Close; +#if defined(HAVE_PACKET) && !defined(HAVE_EPOLL) + newcon->generic.SetFDSets = FTENET_TCP_SetFDSets; #endif - newcon->generic.PrintStatus = FTENET_TCPConnect_PrintStatus; + newcon->generic.PrintStatus = FTENET_TCP_PrintStatus; + newcon->generic.owner = col; newcon->generic.islisten = isserver; newcon->active = 0; @@ -5944,7 +6113,7 @@ ftenet_generic_connection_t *FTENET_TCPConnect_EstablishConnection(qboolean isse newcon->tcpstreams = Z_Malloc(sizeof(*newcon->tcpstreams)); newcon->tcpstreams->next = NULL; newcon->tcpstreams->socketnum = newsocket; - newcon->tcpstreams->clientstream = FS_OpenTCPSocket(newsocket, true, address); + newcon->tcpstreams->clientstream = FS_WrapTCPSocket(newsocket, true, address); newcon->tcpstreams->inlen = 0; newcon->tcpstreams->remoteaddr = adr; @@ -7249,14 +7418,14 @@ qboolean NET_UpdateRates(ftenet_connections_t *collection, qboolean inbound, siz } #endif -/*firstsock is a cookie*/ -int NET_GetPacket (ftenet_connections_t *collection, int firstsock) +void NET_ReadPackets (ftenet_connections_t *collection) { struct ftenet_delayed_packet_s *p; unsigned int ctime; + size_t c = 0; if (!collection) - return -1; + return; while ((p = collection->delayed_packets) && (int)(Sys_Milliseconds()-p->sendtime) > 0) { @@ -7270,10 +7439,11 @@ int NET_GetPacket (ftenet_connections_t *collection, int firstsock) Z_Free(p); } - while (firstsock < MAX_CONNECTIONS) + for (c = 0; c < MAX_CONNECTIONS; c++) { - if (collection->conn[firstsock]) - if (collection->conn[firstsock]->GetPacket(collection->conn[firstsock])) + if (collection->conn[c]) + { + while (collection->conn[c]->GetPacket(collection->conn[c])) { if (net_fakeloss.value) { @@ -7283,11 +7453,10 @@ int NET_GetPacket (ftenet_connections_t *collection, int firstsock) collection->bytesin += net_message.cursize; collection->packetsin += 1; - net_from.connum = firstsock+1; - return firstsock; + net_from.connum = c+1; + collection->ReadGamePacket(); } - - firstsock += 1; + } } ctime = Sys_Milliseconds(); @@ -7304,8 +7473,6 @@ int NET_GetPacket (ftenet_connections_t *collection, int firstsock) collection->bytesout = 0; collection->timemark = ctime; } - - return -1; } int NET_LocalAddressForRemote(ftenet_connections_t *collection, netadr_t *remote, netadr_t *local, int idx) @@ -7592,6 +7759,8 @@ void NET_PrintAddresses(ftenet_connections_t *collection) void NET_PrintConnectionsStatus(ftenet_connections_t *collection) { unsigned int i; + if (!collection) + return; for (i = 0; i < MAX_CONNECTIONS; i++) { if (!collection->conn[i]) @@ -7648,8 +7817,9 @@ int TCP_OpenStream (netadr_t *remoteaddr) sysprot = NSPROTO_IPX; break; #endif + //case NA_UNIX: default: - sysprot = 0; + sysprot = 0; //'auto' break; } temp = NetadrToSockadr(remoteaddr, &qs); @@ -7713,7 +7883,7 @@ int UDP_OpenSocket (int port) struct sockaddr_in address; unsigned long _true = true; int i; -int maxport = port + 100; + int maxport = port + 100; if ((newsocket = socket (PF_INET, SOCK_CLOEXEC|SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET) return (int)INVALID_SOCKET; @@ -7875,6 +8045,54 @@ void IPX_CloseSocket (int socket) } #endif +#ifdef HAVE_EPOLL +static qboolean stdin_ready; +static void StdIn_Now_Ready (struct epollctx_s *ctx, unsigned int events) +{ + stdin_ready = true; +} +qboolean NET_Sleep(float seconds, qboolean stdinissocket) +{ + int waitms; + struct epoll_event waitevents[256]; + static int stdinadded = false; + int n, i; + if (stdinadded != stdinissocket) + { + static epollctx_t stdinctx = {StdIn_Now_Ready}; + struct epoll_event event = {EPOLLIN, {&stdinctx}}; + stdinadded = stdinissocket; + if (stdinissocket) + epoll_ctl(epoll_fd, EPOLL_CTL_ADD, STDIN_FILENO, &event); + else + epoll_ctl(epoll_fd, EPOLL_CTL_DEL, STDIN_FILENO, &event); + } + + waitms = bound(0, (int)(seconds*1000), 4*1000); + n = epoll_wait(epoll_fd, waitevents, countof(waitevents), waitms); + if (n < 0) + { + int err = errno; + switch(err) + { + case EINTR: + break; + default: + Con_Printf("EPoll error: %i\n", err); + break; + } + } + for (i = 0; i < n; i++) + { + struct epoll_event *ev = &waitevents[i]; + struct epollctx_s *ctx = ev->data.ptr; + if (ctx) + ctx->Polled(ctx, ev->events); + //else edge-triggered events can be processed as part of the main loop + } + return stdin_ready; +} +#else // sleeps msec or until net socket is ready //stdin can sometimes be a socket. As a result, //we give the option to select it for nice console imput with timeouts. @@ -7893,7 +8111,7 @@ qboolean NET_Sleep(float seconds, qboolean stdinissocket) if (stdinissocket) { - sock = 0; //stdin tends to be socket/filehandle 0 in unix + sock = STDIN_FILENO; //stdin tends to be socket/filehandle 0 in unix FD_SET(sock, &readfdset); maxfd = sock; } @@ -7950,8 +8168,7 @@ qboolean NET_Sleep(float seconds, qboolean stdinissocket) } #endif - if (seconds > 4.0) //realy? oh well. - seconds = 4.0; + seconds = bound(0.0, seconds, 4.0); //realy? oh well. if (maxfd == -1) Sys_Sleep(seconds); else @@ -7964,10 +8181,11 @@ qboolean NET_Sleep(float seconds, qboolean stdinissocket) } if (stdinissocket) - return FD_ISSET(0, &readfdset); + return FD_ISSET(STDIN_FILENO, &readfdset); #endif return true; } +#endif //this function is used to determine the 'default' local address. //this is used for compat with gamespy which insists on sending us a packet via that interface and not something more sensible like 127.0.0.1 @@ -8057,7 +8275,7 @@ void SVNET_AddPort_f(void) //just in case if (!svs.sockets) { - svs.sockets = FTENET_CreateCollection(true); + svs.sockets = FTENET_CreateCollection(true, SV_ReadPacket); #ifdef HAVE_CLIENT FTENET_AddToCollection(svs.sockets, "SVLoopback", STRINGIFY(PORT_DEFAULTSERVER), NA_LOOPBACK, NP_DGRAM); #endif @@ -8104,6 +8322,10 @@ NET_Init */ void NET_Init (void) { +#ifdef HAVE_EPOLL + epoll_fd = epoll_create1(EPOLL_CLOEXEC); +#endif + Cvar_Register(&net_enabled, "networking"); Cvar_Register(&net_dns_ipv4, "networking"); Cvar_Register(&net_dns_ipv6, "networking"); @@ -8204,7 +8426,7 @@ void NET_InitClient(qboolean loopbackonly) } if (!cls.sockets) - cls.sockets = FTENET_CreateCollection(false); + cls.sockets = FTENET_CreateCollection(false, CL_ReadPacket); #ifdef HAVE_SERVER FTENET_AddToCollection(cls.sockets, "CLLoopback", "1", NA_LOOPBACK, NP_DGRAM); #endif @@ -8368,7 +8590,7 @@ void NET_InitServer(void) { if (!svs.sockets) { - svs.sockets = FTENET_CreateCollection(true); + svs.sockets = FTENET_CreateCollection(true, SV_ReadPacket); #ifdef HAVE_CLIENT FTENET_AddToCollection(svs.sockets, "SVLoopback", STRINGIFY(PORT_DEFAULTSERVER), NA_LOOPBACK, NP_DGRAM); #endif @@ -8413,7 +8635,7 @@ void NET_InitServer(void) NET_CloseServer(); #ifdef HAVE_CLIENT - svs.sockets = FTENET_CreateCollection(true); + svs.sockets = FTENET_CreateCollection(true, SV_ReadPacket); FTENET_AddToCollection(svs.sockets, "SVLoopback", STRINGIFY(PORT_DEFAULTSERVER), NA_LOOPBACK, NP_DGRAM); #endif } @@ -8442,6 +8664,12 @@ void NET_Shutdown (void) #endif +#ifdef HAVE_EPOLL + close(epoll_fd); + epoll_fd = -1; +#endif + + #if defined(_WIN32) && defined(HAVE_PACKET) #ifdef SERVERTONLY if (!serverthreadID) //running as subsystem of client. Don't close all of it's sockets too. @@ -8461,19 +8689,13 @@ typedef struct { SOCKET sock; qboolean conpending; + qboolean readaborted; //some kind of error. don't spam + qboolean writeaborted; //some kind of error. don't spam char readbuffer[65536]; int readbuffered; char peer[1]; } tcpfile_t; -static void VFSTCP_Error(tcpfile_t *f) -{ - if (f->sock != INVALID_SOCKET) - { - closesocket(f->sock); - f->sock = INVALID_SOCKET; - } -} int QDECL VFSTCP_ReadBytes (struct vfsfile_s *file, void *buffer, int bytestoread) { tcpfile_t *tf = (tcpfile_t*)file; @@ -8496,7 +8718,7 @@ int QDECL VFSTCP_ReadBytes (struct vfsfile_s *file, void *buffer, int bytestorea tf->conpending = false; } - if (tf->sock != INVALID_SOCKET) + if (!tf->readaborted) { trying = sizeof(tf->readbuffer) - tf->readbuffered; if (bytestoread > 1500) @@ -8513,7 +8735,7 @@ int QDECL VFSTCP_ReadBytes (struct vfsfile_s *file, void *buffer, int bytestorea if (len == -1) { int e = neterrno(); - if (e != NET_EWOULDBLOCK) + if (e != NET_EWOULDBLOCK && e != NET_EINTR) { switch(e) { @@ -8535,14 +8757,14 @@ int QDECL VFSTCP_ReadBytes (struct vfsfile_s *file, void *buffer, int bytestorea default: Con_Printf("tcp socket error %i (%s)\n", e, tf->peer); } - VFSTCP_Error(tf); + tf->readaborted = true; } //fixme: figure out wouldblock or error } else if (len == 0 && trying != 0) { //peer disconnected - VFSTCP_Error(tf); + tf->readaborted = true; } else { @@ -8554,7 +8776,7 @@ int QDECL VFSTCP_ReadBytes (struct vfsfile_s *file, void *buffer, int bytestorea if (bytestoread > tf->readbuffered) bytestoread = tf->readbuffered; if (bytestoread < 0) - VFSTCP_Error(tf); + return -1; //caller error... if (bytestoread > 0) { @@ -8565,7 +8787,7 @@ int QDECL VFSTCP_ReadBytes (struct vfsfile_s *file, void *buffer, int bytestorea } else { - if (tf->sock == INVALID_SOCKET) + if (tf->readaborted) { return -1; //signal an error } @@ -8577,7 +8799,7 @@ int QDECL VFSTCP_WriteBytes (struct vfsfile_s *file, const void *buffer, int byt tcpfile_t *tf = (tcpfile_t*)file; int len; - if (tf->sock == INVALID_SOCKET) + if (tf->writeaborted) return -1; if (tf->conpending) @@ -8590,6 +8812,7 @@ int QDECL VFSTCP_WriteBytes (struct vfsfile_s *file, const void *buffer, int byt FD_SET(tf->sock, &fdw); FD_ZERO(&fdx); FD_SET(tf->sock, &fdx); + //check if we can actually write to it yet, without generating weird errors... if (!select((int)tf->sock+1, NULL, &fdw, &fdx, &timeout)) return 0; tf->conpending = false; @@ -8598,14 +8821,18 @@ int QDECL VFSTCP_WriteBytes (struct vfsfile_s *file, const void *buffer, int byt len = send(tf->sock, buffer, bytestoread, 0); if (len == -1 || len == 0) { - int e = neterrno(); + int e = (len==0)?NET_ECONNABORTED:neterrno(); switch(e) { + case NET_EINTR: case NET_EWOULDBLOCK: return 0; //nothing available yet. case NET_ETIMEDOUT: Con_Printf("connection to \"%s\" timed out\n", tf->peer); return -1; //don't bother trying to read if we never connected. + case NET_ECONNABORTED: + Con_Printf("connection to \"%s\" aborted\n", tf->peer); + break; case NET_ENOTCONN: #ifdef __unix__ case EPIPE: @@ -8619,18 +8846,17 @@ int QDECL VFSTCP_WriteBytes (struct vfsfile_s *file, const void *buffer, int byt // don't destroy it on write errors, because that prevents us from reading anything that was sent to us afterwards. // instead let the read handling kill it if there's nothing new to be read VFSTCP_ReadBytes(file, NULL, 0); + tf->writeaborted = true; return -1; } return len; } qboolean QDECL VFSTCP_Seek (struct vfsfile_s *file, qofs_t pos) { - VFSTCP_Error((tcpfile_t*)file); return false; } static qofs_t QDECL VFSTCP_Tell (struct vfsfile_s *file) { - VFSTCP_Error((tcpfile_t*)file); return 0; } static qofs_t QDECL VFSTCP_GetLen (struct vfsfile_s *file) @@ -8641,12 +8867,16 @@ static qboolean QDECL VFSTCP_Close (struct vfsfile_s *file) { tcpfile_t *f = (tcpfile_t *)file; qboolean success = f->sock != INVALID_SOCKET; - VFSTCP_Error(f); + if (f->sock != INVALID_SOCKET) + { + closesocket(f->sock); + f->sock = INVALID_SOCKET; + } Z_Free(f); return success; } -vfsfile_t *FS_OpenTCPSocket(SOCKET sock, qboolean conpending, const char *peername) +vfsfile_t *FS_WrapTCPSocket(SOCKET sock, qboolean conpending, const char *peername) { tcpfile_t *newf; if (sock == INVALID_SOCKET) @@ -8672,7 +8902,7 @@ vfsfile_t *FS_OpenTCP(const char *name, int defaultport) netadr_t adr = {0}; if (NET_StringToAdr(name, defaultport, &adr)) { - return FS_OpenTCPSocket(TCP_OpenStream(&adr), true, name); + return FS_WrapTCPSocket(TCP_OpenStream(&adr), true, name); } else return NULL; diff --git a/engine/common/netinc.h b/engine/common/netinc.h index 9fd85aa3b..3b04669ed 100644 --- a/engine/common/netinc.h +++ b/engine/common/netinc.h @@ -142,6 +142,14 @@ #include #endif + #ifdef __linux__ + //requires linux 2.6.27 up (and equivelent libc) + //note that BSD does tend to support the api, but emulated. + //this works around the select FD limit, and supposedly has better performance. + #define HAVE_EPOLL + #include + #endif + #if defined(__MORPHOS__) && !defined(ixemul) #define closesocket CloseSocket #define ioctlsocket IoctlSocket @@ -173,6 +181,7 @@ #define neterrno() WSAGetLastError() //this madness is because winsock defines its own errors instead of using system error codes. //*AND* microsoft then went and defined names for all the unix ones too... with different values! oh the insanity of it all! + #define NET_EINTR WSAEINTR #define NET_EWOULDBLOCK WSAEWOULDBLOCK #define NET_EINPROGRESS WSAEINPROGRESS #define NET_EMSGSIZE WSAEMSGSIZE @@ -193,6 +202,7 @@ #ifndef NET_EWOULDBLOCK //assume unix codes instead, so our prefix still works. + #define NET_EINTR EINTR #define NET_EWOULDBLOCK EWOULDBLOCK #define NET_EINPROGRESS EINPROGRESS #define NET_EMSGSIZE EMSGSIZE @@ -280,6 +290,13 @@ typedef struct extern icefuncs_t iceapi; #endif +#ifdef HAVE_EPOLL +typedef struct epollctx_s +{ + void (*Polled) (struct epollctx_s *ctx, unsigned int events); +} epollctx_t; +#endif + //address flags #define ADDR_NATPMP (1u<<0) #define ADDR_UPNPIGP (1u<<1) @@ -294,13 +311,22 @@ typedef struct ftenet_generic_connection_s { qboolean (*GetPacket)(struct ftenet_generic_connection_s *con); neterr_t (*SendPacket)(struct ftenet_generic_connection_s *con, int length, const void *data, netadr_t *to); void (*Close)(struct ftenet_generic_connection_s *con); -#ifdef HAVE_PACKET +#if defined(HAVE_PACKET) && !defined(HAVE_EPOLL) int (*SetFDSets) (struct ftenet_generic_connection_s *con, fd_set *readfdset, fd_set *writefdset); /*set for connections which have multiple sockets (ie: listening tcp connections)*/ #endif void (*PrintStatus)(struct ftenet_generic_connection_s *con); - netadrtype_t addrtype[FTENET_ADDRTYPES]; + netproto_t prot; //if there's some special weirdness + netadrtype_t addrtype[FTENET_ADDRTYPES]; //which address families it accepts qboolean islisten; + + int connum; + struct ftenet_connections_s *owner; + +#ifdef HAVE_EPOLL + epollctx_t epoll; +#endif + #ifdef HAVE_PACKET SOCKET thesocket; #else @@ -357,6 +383,8 @@ typedef struct ftenet_connections_s float bytesoutrate; ftenet_generic_connection_t *conn[MAX_CONNECTIONS]; + void (*ReadGamePacket) (void); + #ifdef HAVE_DTLS struct dtlspeer_s *dtls; //linked list. linked lists are shit, but at least it keeps pointers valid when things are resized. const dtlsfuncs_t *dtlsfuncs; @@ -376,7 +404,7 @@ void ICE_Tick(void); qboolean ICE_WasStun(ftenet_connections_t *col); void QDECL ICE_AddLCandidateConn(ftenet_connections_t *col, netadr_t *addr, int type); void QDECL ICE_AddLCandidateInfo(struct icestate_s *con, netadr_t *adr, int adrno, int type); -ftenet_generic_connection_t *FTENET_ICE_EstablishConnection(qboolean isserver, const char *address, netadr_t adr); +ftenet_generic_connection_t *FTENET_ICE_EstablishConnection(ftenet_connections_t *col, const char *address, netadr_t adr); enum icemsgtype_s { //shared by rtcpeers+broker ICEMSG_PEERDROP=0, //other side dropped connection @@ -399,7 +427,7 @@ enum websocketpackettype_e WS_PACKETTYPE_PONG=10, }; -ftenet_connections_t *FTENET_CreateCollection(qboolean listen); +ftenet_connections_t *FTENET_CreateCollection(qboolean listen, void (*ReadPacket) (void)); void FTENET_CloseCollection(ftenet_connections_t *col); qboolean FTENET_AddToCollection(struct ftenet_connections_s *col, const char *name, const char *address, netadrtype_t addrtype, netproto_t addrprot); int NET_EnumerateAddresses(ftenet_connections_t *collection, struct ftenet_generic_connection_s **con, unsigned int *adrflags, netadr_t *addresses, const char **adrparams, int maxaddresses); @@ -408,7 +436,7 @@ void *TLS_GetKnownCertificate(const char *certname, size_t *size); vfsfile_t *FS_OpenSSL(const char *hostname, vfsfile_t *source, qboolean server); int TLS_GetChannelBinding(vfsfile_t *stream, qbyte *data, size_t *datasize); //datasize should be preinitialised to the max length allowed. -1 for not implemented. 0 for peer problems. 1 for success #ifdef HAVE_PACKET -vfsfile_t *FS_OpenTCPSocket(SOCKET socket, qboolean conpending, const char *peername); //conpending allows us to reject any writes until the connection has succeeded +vfsfile_t *FS_WrapTCPSocket(SOCKET socket, qboolean conpending, const char *peername); //conpending allows us to reject any writes until the connection has succeeded. considers the socket owned (so be sure to stop using the direct socket at least before the VFS_CLOSE call). #endif vfsfile_t *FS_OpenTCP(const char *name, int defaultport); diff --git a/engine/common/zone.c b/engine/common/zone.c index ae45006ff..280059acc 100644 --- a/engine/common/zone.c +++ b/engine/common/zone.c @@ -47,7 +47,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #endif #if ZONEDEBUG>0 || HUNKDEBUG>0 || TEMPDEBUG>0 -qbyte sentinalkey; +qbyte sentinelkey; #endif #define TAGLESS 1 @@ -568,16 +568,16 @@ void Hunk_TempFree(void) buf = (qbyte *)(hnktemps+1); for (i = 0; i < TEMPDEBUG; i++) { - if (buf[i] != sentinalkey) - Sys_Error ("Hunk_Check: corrupt sentinal"); + if (buf[i] != sentinelkey) + Sys_Error ("Hunk_Check: corrupt sentinel"); } buf+=TEMPDEBUG; //app data buf += hnktemps->len; for (i = 0; i < TEMPDEBUG; i++) { - if (buf[i] != sentinalkey) - Sys_Error ("Hunk_Check: corrupt sentinal"); + if (buf[i] != sentinelkey) + Sys_Error ("Hunk_Check: corrupt sentinel"); } #endif @@ -605,10 +605,10 @@ void *Hunk_TempAllocMore (size_t size) nt->len = size; hnktemps = nt; buf = (void *)(nt+1); - memset(buf, sentinalkey, TEMPDEBUG); + memset(buf, sentinelkey, TEMPDEBUG); buf = (char *)buf + TEMPDEBUG; memset(buf, 0, size); - memset((char *)buf + size, sentinalkey, TEMPDEBUG); + memset((char *)buf + size, sentinelkey, TEMPDEBUG); return buf; #else hnktemps_t *nt; @@ -717,7 +717,7 @@ void Memory_Init (void) #if ZONEDEBUG>0 || HUNKDEBUG>0 || TEMPDEBUG>0||CACHEDEBUG>0 srand(time(0)); - sentinalkey = rand() & 0xff; + sentinelkey = rand() & 0xff; #endif Cache_Init (); diff --git a/engine/gl/gl_backend.c b/engine/gl/gl_backend.c index fa146d847..9796b879c 100644 --- a/engine/gl/gl_backend.c +++ b/engine/gl/gl_backend.c @@ -5571,6 +5571,7 @@ static void BE_UpdateLightmaps(void) { extern cvar_t r_lightmap_nearest; TEXASSIGN(lm->lightmap_texture, Image_CreateTexture(va("***lightmap %i***", lmidx), NULL, (r_lightmap_nearest.ival?IF_NEAREST:IF_LINEAR)|IF_NOMIPMAP)); + lm->lightmap_texture->format = lm->fmt; qglGenTextures(1, &lm->lightmap_texture->num); GL_MTBind(0, GL_TEXTURE_2D, lm->lightmap_texture); qglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); diff --git a/engine/gl/gl_font.c b/engine/gl/gl_font.c index 3b27d5569..bfa4ca73e 100644 --- a/engine/gl/gl_font.c +++ b/engine/gl/gl_font.c @@ -1578,7 +1578,7 @@ qboolean Font_LoadFreeTypeFont(struct font_s *f, int height, const char *fontfil } error = FT_Err_Cannot_Open_Resource; - if (FS_FLocateFile(fontfilename, FSLF_IFFOUND, &loc) || FS_FLocateFile(va("%s.ttf", fontfilename), FSLF_IFFOUND, &loc) || FS_FLocateFile(va("%s.otf", fontfilename), FSLF_IFFOUND, &loc)) + if (FS_FLocateFile(fontfilename, FSLF_IFFOUND|FSLF_QUIET, &loc) || FS_FLocateFile(va("%s.ttf", fontfilename), FSLF_IFFOUND|FSLF_QUIET, &loc) || FS_FLocateFile(va("%s.otf", fontfilename), FSLF_IFFOUND|FSLF_QUIET, &loc)) { if (*loc.rawname && !loc.offset) { diff --git a/engine/gl/gl_model.c b/engine/gl/gl_model.c index 2f51cd46d..6fc821021 100644 --- a/engine/gl/gl_model.c +++ b/engine/gl/gl_model.c @@ -2579,10 +2579,16 @@ static void Mod_Batches_BuildModelMeshes(model_t *mod, int maxverts, int maxindi surf->lightmaptexturenums[sty] /= 2; if (mesh->lmst_array[sty]) { + int soffset = surf->lightmaptexturenums[sty] % mod->lightmaps.mergew; + int toffset = surf->lightmaptexturenums[sty] / mod->lightmaps.mergew; + float smul = 1.0/mod->lightmaps.mergew; + float tmul = 1.0/mod->lightmaps.mergeh; for (i = 0; i < mesh->numvertexes; i++) { - mesh->lmst_array[sty][i][1] += surf->lightmaptexturenums[sty] % lmmerge; - mesh->lmst_array[sty][i][1] /= lmmerge; + mesh->lmst_array[sty][i][0] += soffset; + mesh->lmst_array[sty][i][0] *= smul; + mesh->lmst_array[sty][i][1] += toffset; + mesh->lmst_array[sty][i][1] *= tmul; } } surf->lightmaptexturenums[sty] /= lmmerge; @@ -2670,9 +2676,9 @@ static int Mod_Batches_Generate(model_t *mod) vec4_t plane; image_t *envmap; - int merge = mod->lightmaps.merge; + int merge = mod->lightmaps.mergew*mod->lightmaps.mergeh; if (!merge) - merge = 1; + merge = mod->lightmaps.mergew = mod->lightmaps.mergeh = 1; //no division by 0 please... if (mod->lightmaps.deluxemapping) { mod->lightmaps.count = ((mod->lightmaps.count+1)/2+merge-1) & ~(merge-1); @@ -2684,7 +2690,8 @@ static int Mod_Batches_Generate(model_t *mod) mod->lightmaps.count = (mod->lightmaps.count+merge-1) & ~(merge-1); mod->lightmaps.count /= merge; } - mod->lightmaps.height *= merge; + mod->lightmaps.width *= mod->lightmaps.mergew; + mod->lightmaps.height *= mod->lightmaps.mergeh; mod->numbatches = 0; diff --git a/engine/gl/gl_model.h b/engine/gl/gl_model.h index a7cb3eee5..09b965d5f 100644 --- a/engine/gl/gl_model.h +++ b/engine/gl/gl_model.h @@ -1022,7 +1022,8 @@ typedef struct model_s { int first; //once built... int count; //num lightmaps - int merge; //merge this many source lightmaps together. woo. + int mergew; //merge this many source lightmaps together. woo. + int mergeh; //merge this many source lightmaps together. woo. int width; //x size of lightmaps int height; //y size of lightmaps int surfstyles; //numbers of style per surface. diff --git a/engine/gl/gl_rsurf.c b/engine/gl/gl_rsurf.c index c653102b7..eed494afc 100644 --- a/engine/gl/gl_rsurf.c +++ b/engine/gl/gl_rsurf.c @@ -578,8 +578,10 @@ void GLBE_UploadAllLightmaps(void) } //for completeness. + lm->lightmap_texture->format = lm->fmt; lm->lightmap_texture->width = lm->width; lm->lightmap_texture->height = lm->height; + lm->lightmap_texture->depth = 1; lm->lightmap_texture->status = TEX_LOADED; } } diff --git a/engine/http/httpclient.c b/engine/http/httpclient.c index b7ce9a8c2..48c23da8e 100644 --- a/engine/http/httpclient.c +++ b/engine/http/httpclient.c @@ -1056,7 +1056,7 @@ void HTTPDL_Establish(struct dl_download *dl) //https uses a different default port if (NET_StringToAdr2(con->server, https?443:80, &adr, 1, NULL)) con->sock = TCP_OpenStream(&adr); - con->stream = FS_OpenTCPSocket(con->sock, true, con->server); + con->stream = FS_WrapTCPSocket(con->sock, true, con->server); } #ifdef HAVE_SSL if (https) diff --git a/engine/server/server.h b/engine/server/server.h index 202c4ade4..11cced633 100644 --- a/engine/server/server.h +++ b/engine/server/server.h @@ -1142,6 +1142,7 @@ void SV_AutoAddPenalty (client_t *cl, unsigned int banflag, int duration, char * NORETURN void VARGS SV_Error (char *error, ...) LIKEPRINTF(1); void SV_Shutdown (void); float SV_Frame (void); +void SV_ReadPacket(void); void SV_FinalMessage (char *message); void SV_DropClient (client_t *drop); struct quakeparms_s; diff --git a/engine/server/sv_main.c b/engine/server/sv_main.c index 8315079f0..162657974 100644 --- a/engine/server/sv_main.c +++ b/engine/server/sv_main.c @@ -2760,7 +2760,7 @@ void SV_DoDirectConnect(svconnectinfo_t *fte_restrict info) case GT_PROGS: if (info->protocol == SCP_QUAKE2) { - SV_RejectMessage(info->protocol, "This is a Quake server."); + SV_RejectMessage(info->protocol, "This is a %s server.", fs_manifest->formalname); Con_DPrintf ("* Rejected q2 client.\n"); return; } @@ -2790,7 +2790,7 @@ void SV_DoDirectConnect(svconnectinfo_t *fte_restrict info) case GT_QUAKE2: if (info->protocol != SCP_QUAKE2) { - SV_RejectMessage(info->protocol, "This is a Quake2 server."); + SV_RejectMessage(info->protocol, "This is a %s server.", fs_manifest->formalname); Con_DPrintf ("* Rejected non-q2 client.\n"); return; } @@ -3843,7 +3843,10 @@ qboolean SV_ConnectionlessPacket (void) Con_Printf("%s: %s\n", NET_AdrToString (adr, sizeof(adr), &net_from), s); if (!strcmp(c, "ping") || ( c[0] == A2A_PING && (c[1] == 0 || c[1] == '\n')) ) - SVC_Ping (); + { //only continue respond to these if we're actually public. qwfwd likes spamming us endlessly even if we stop heartbeating (which leaves us discoverable to others, too). + if (sv_public.ival >= 0) + SVC_Ping (); + } else if (c[0] == A2A_ACK && (c[1] == 0 || c[1] == '\n') ) SVC_ACK (); else if (!strcmp(c,"status")) @@ -4384,6 +4387,189 @@ void SV_OpenRoute_f(void) } //============================================================================ +static int inboundsequence; //so we can detect frames when we didn't get any packets, even when packets come from epoll +void SV_ReadPacket(void) +{ + int i; + client_t *cl; + int qport; + char *banreason; + + // check for connectionless packet (0xffffffff) first + if (*(unsigned int *)net_message.data == ~0) + { + banreason = SV_BannedReason (&net_from); + if (banreason) + { + static unsigned int lt; + unsigned int ct = Sys_Milliseconds(); + if (ct - lt > 5*1000) + { + if (*banreason) + Netchan_OutOfBandTPrintf(NS_SERVER, &net_from, com_language, "You are banned: %s\n", banreason); + else + Netchan_OutOfBandTPrintf(NS_SERVER, &net_from, com_language, "You are banned\n"); + } + return; + } + + SV_ConnectionlessPacket(); + return; + } +#ifdef HAVE_DTLS + else + { + if (NET_DTLS_Decode(svs.sockets)) + { + if (!net_message.cursize) + return; + if (*(unsigned int *)net_message.data == ~0) + { + SV_ConnectionlessPacket(); + return; + } + } + } +#endif + +#ifdef Q3SERVER + if (svs.gametype == GT_QUAKE3) + { + if (SVQ3_HandleClient()) + inboundsequence++; + else if (NET_WasSpecialPacket(svs.sockets)) + return; + return; + } +#endif + + // read the qport out of the message so we can fix up + // stupid address translating routers + MSG_BeginReading (svs.netprim); + MSG_ReadLong (); // sequence number + MSG_ReadLong (); // sequence number + qport = MSG_ReadShort () & 0xffff; + + // check for packets from connected clients + for (i=0, cl=svs.clients ; istate == cs_free) + continue; + if (!NET_CompareBaseAdr (&net_from, &cl->netchan.remote_address)) + continue; +#ifdef NQPROT + if (ISNQCLIENT(cl) && cl->netchan.remote_address.port == net_from.port) + { + if (cl->state >= cs_connected) + { + if (cl->delay > 0) + goto dominping; + + if (NQNetChan_Process(&cl->netchan)) + { + inboundsequence++; + svs.stats.packets++; + SVNQ_ExecuteClientMessage(cl); + } + } + break; + } +#endif + +#ifdef Q3SERVER + if (ISQ3CLIENT(cl)) + continue; +#endif + + if (cl->netchan.qport != qport) + continue; + if (cl->netchan.remote_address.port != net_from.port) + { + Con_DPrintf ("SV_ReadPackets: fixing up a translated port\n"); + cl->netchan.remote_address.port = net_from.port; + } + + if (cl->delay > 0) + { +#ifdef NQPROT +dominping: +#endif + if (cl->state < cs_connected) + break; + if (net_message.cursize > sizeof(svs.free_lagged_packet->data)) + { + Con_Printf("packet too large for minping\n"); + cl->delay -= 0.001; + break; //drop this packet + } + + if (!svs.free_lagged_packet) //kinda nasty + svs.free_lagged_packet = Z_Malloc(sizeof(*svs.free_lagged_packet)); + + if (!cl->laggedpacket) + cl->laggedpacket_last = cl->laggedpacket = svs.free_lagged_packet; + else + { + cl->laggedpacket_last->next = svs.free_lagged_packet; + cl->laggedpacket_last = cl->laggedpacket_last->next; + } + svs.free_lagged_packet = svs.free_lagged_packet->next; + cl->laggedpacket_last->next = NULL; + + cl->laggedpacket_last->time = realtime + cl->delay; + memcpy(cl->laggedpacket_last->data, net_message.data, net_message.cursize); + cl->laggedpacket_last->length = net_message.cursize; + break; + } + + + if (Netchan_Process(&cl->netchan)) + { // this is a valid, sequenced packet, so process it + inboundsequence++; + svs.stats.packets++; + if (cl->state >= cs_connected) + { + if (cl->send_message) + cl->chokecount++; + else + cl->send_message = true; // reply at end of frame + +#ifdef Q2SERVER + if (cl->protocol == SCP_QUAKE2) + SVQ2_ExecuteClientMessage(cl); + else +#endif + SV_ExecuteClientMessage (cl); + } + } + break; + } + + if (i != svs.allocated_client_slots) + return; + +#ifdef QWOVERQ3 + if (sv_listen_q3.ival && SVQ3_HandleClient()) + { + received++; + continue; + } +#endif + +#ifdef NQPROT + if (SVNQ_ConnectionlessPacket()) + return; +#endif + if (SV_BannedReason (&net_from)) + return; + + if (NET_WasSpecialPacket(svs.sockets)) + return; + + // packet is not from a known client + if (sv_showconnectionlessmessages.ival) + Con_Printf ("%s:sequenced packet without connection\n", NET_AdrToString (com_token, sizeof(com_token), &net_from)); //hack: com_token cos we need some random temp buffer. +} /* ================= @@ -4399,12 +4585,9 @@ qboolean SV_ReadPackets (float *delay) { int i; client_t *cl; - int qport; laggedpacket_t *lp; - char *banreason; - qboolean received = false; - int giveup = 5000; /*we're fucked if we need this to be this high, but at least we can retain some clients if we're really running that slow*/ - int cookie = 0; + + static int oldinboundsequence; SV_KillExpiredBans(); @@ -4443,7 +4626,7 @@ qboolean SV_ReadPackets (float *delay) { if (NQNetChan_Process(&cl->netchan)) { - received++; + inboundsequence++; svs.stats.packets++; SVNQ_ExecuteClientMessage(cl); } @@ -4455,7 +4638,7 @@ qboolean SV_ReadPackets (float *delay) /*QW*/ if (Netchan_Process(&cl->netchan)) { // this is a valid, sequenced packet, so process it - received++; + inboundsequence++; svs.stats.packets++; if (cl->state >= cs_connected) { //make sure they didn't already disconnect @@ -4477,194 +4660,17 @@ qboolean SV_ReadPackets (float *delay) } } -#ifdef SERVER_DEMO_PLAYBACK - while (giveup-- > 0 && SV_GetPacket()>=0) -#else - while (giveup-- > 0 && (cookie=NET_GetPacket (svs.sockets, cookie)) >= 0) -#endif - { - // check for connectionless packet (0xffffffff) first - if (*(unsigned int *)net_message.data == ~0) - { - banreason = SV_BannedReason (&net_from); - if (banreason) - { - static unsigned int lt; - unsigned int ct = Sys_Milliseconds(); - if (ct - lt > 5*1000) - { - if (*banreason) - Netchan_OutOfBandTPrintf(NS_SERVER, &net_from, com_language, "You are banned: %s\n", banreason); - else - Netchan_OutOfBandTPrintf(NS_SERVER, &net_from, com_language, "You are banned\n"); - } - continue; - } - - SV_ConnectionlessPacket(); - continue; - } -#ifdef HAVE_DTLS - else - { - if (NET_DTLS_Decode(svs.sockets)) - { - if (!net_message.cursize) - continue; - if (*(unsigned int *)net_message.data == ~0) - { - SV_ConnectionlessPacket(); - continue; - } - } - } -#endif - -#ifdef Q3SERVER - if (svs.gametype == GT_QUAKE3) - { - received++; - if (SVQ3_HandleClient()) - ; - else if (NET_WasSpecialPacket(svs.sockets)) - continue; - continue; - } -#endif - - // read the qport out of the message so we can fix up - // stupid address translating routers - MSG_BeginReading (svs.netprim); - MSG_ReadLong (); // sequence number - MSG_ReadLong (); // sequence number - qport = MSG_ReadShort () & 0xffff; - - // check for packets from connected clients - for (i=0, cl=svs.clients ; istate == cs_free) - continue; - if (!NET_CompareBaseAdr (&net_from, &cl->netchan.remote_address)) - continue; -#ifdef NQPROT - if (ISNQCLIENT(cl) && cl->netchan.remote_address.port == net_from.port) - { - if (cl->state >= cs_connected) - { - if (cl->delay > 0) - goto dominping; - - if (NQNetChan_Process(&cl->netchan)) - { - received++; - svs.stats.packets++; - SVNQ_ExecuteClientMessage(cl); - } - } - break; - } -#endif - -#ifdef Q3SERVER - if (ISQ3CLIENT(cl)) - continue; -#endif - - if (cl->netchan.qport != qport) - continue; - if (cl->netchan.remote_address.port != net_from.port) - { - Con_DPrintf ("SV_ReadPackets: fixing up a translated port\n"); - cl->netchan.remote_address.port = net_from.port; - } - - if (cl->delay > 0) - { -#ifdef NQPROT -dominping: -#endif - if (cl->state < cs_connected) - break; - if (net_message.cursize > sizeof(svs.free_lagged_packet->data)) - { - Con_Printf("packet too large for minping\n"); - cl->delay -= 0.001; - break; //drop this packet - } - - if (!svs.free_lagged_packet) //kinda nasty - svs.free_lagged_packet = Z_Malloc(sizeof(*svs.free_lagged_packet)); - - if (!cl->laggedpacket) - cl->laggedpacket_last = cl->laggedpacket = svs.free_lagged_packet; - else - { - cl->laggedpacket_last->next = svs.free_lagged_packet; - cl->laggedpacket_last = cl->laggedpacket_last->next; - } - svs.free_lagged_packet = svs.free_lagged_packet->next; - cl->laggedpacket_last->next = NULL; - - cl->laggedpacket_last->time = realtime + cl->delay; - memcpy(cl->laggedpacket_last->data, net_message.data, net_message.cursize); - cl->laggedpacket_last->length = net_message.cursize; - break; - } - - - if (Netchan_Process(&cl->netchan)) - { // this is a valid, sequenced packet, so process it - received++; - svs.stats.packets++; - if (cl->state >= cs_connected) - { - if (cl->send_message) - cl->chokecount++; - else - cl->send_message = true; // reply at end of frame - -#ifdef Q2SERVER - if (cl->protocol == SCP_QUAKE2) - SVQ2_ExecuteClientMessage(cl); - else -#endif - SV_ExecuteClientMessage (cl); - } - } - break; - } - - if (i != svs.allocated_client_slots) - continue; - -#ifdef QWOVERQ3 - if (sv_listen_q3.ival && SVQ3_HandleClient()) - { - received++; - continue; - } -#endif - -#ifdef NQPROT - if (SVNQ_ConnectionlessPacket()) - continue; -#endif - if (SV_BannedReason (&net_from)) - continue; - - if (NET_WasSpecialPacket(svs.sockets)) - continue; - - // packet is not from a known client - if (sv_showconnectionlessmessages.ival) - Con_Printf ("%s:sequenced packet without connection\n", NET_AdrToString (com_token, sizeof(com_token), &net_from)); //hack: com_token cos we need some random temp buffer. - } + NET_ReadPackets(svs.sockets); #ifdef HAVE_DTLS NET_DTLS_Timeouts(svs.sockets); #endif - return received; + + if (inboundsequence == oldinboundsequence) + return false; //nothing new. + oldinboundsequence = inboundsequence; + return true; } /* diff --git a/engine/server/sv_master.c b/engine/server/sv_master.c index 2d385c9a4..4bc68f750 100644 --- a/engine/server/sv_master.c +++ b/engine/server/sv_master.c @@ -10,21 +10,25 @@ #endif #include "netinc.h" +#include "fs.h" //quakeworld protocol // heartbeat: "a" // query: "c\n%i\n%i\n" //queryresponse: "d\naaaappaaaapp" +#define QUAKEWORLDPROTOCOLNAME "FTE-Quake" //quake2 protocol // heartbeat: "heartbeat\n%s\n%i %i \"%s\"\n" // query: "query\0" //queryresponse: "servers\naaaappaaaapp" +#define QUAKE2PROTOCOLNAME "Quake2" //quake3/dpmaster protocol // heartbeat: "heartbeat DarkPlaces\n" // query: "getservers[Ext] [%s] %u [empty] [full] [ipv6]" //queryresponse: "getservers[Ext]Response\\aaaapp/aaaaaaaaaaaapp\\EOF" +#define QUAKE3PROTOCOLNAME "Quake3" enum gametypes_e { @@ -39,6 +43,7 @@ typedef struct svm_server_s { int protover; unsigned int clients; unsigned int maxclients; + qboolean needpass; char hostname[48]; //just for our own listings. char mapname[16]; //just for our own listings. char gamedir[16]; //again... @@ -56,7 +61,8 @@ typedef struct svm_game_s { svm_server_t *firstserver; size_t numservers; qboolean persistent; - char name[1]; + char *aliases; //list of terminated names, terminated with a double-null + char name[1]; //eg: Quake } svm_game_t; typedef struct { @@ -93,11 +99,15 @@ static void QDECL SVM_Port_Callback(struct cvar_s *var, char *oldvalue) { FTENET_AddToCollection(svm_sockets, var->name, var->string, NA_IP, NP_DGRAM); } -static cvar_t sv_heartbeattimeout = CVARD("sv_heartbeattimeout", "600", "How many seconds a server should remain listed after its latest heartbeat. Larger values can avoid issues from packetloss, but can also make dos attacks easier."); +static cvar_t sv_heartbeattimeout = CVARD("sv_heartbeattimeout", "300", "How many seconds a server should remain listed after its latest heartbeat. Larger values can avoid issues from packetloss, but can also make dos attacks easier."); static cvar_t sv_masterport = CVARC("sv_masterport", STRINGIFY(PORT_QWMASTER)" "STRINGIFY(PORT_ICEBROKER), SVM_Port_Callback); static cvar_t sv_masterport_tcp = CVARC("sv_masterport_tcp", STRINGIFY(PORT_ICEBROKER), SVM_Tcpport_Callback); static cvar_t sv_maxgames = CVARD("sv_maxgames", "100", "Limits the number of games that may be known. This is to reduce denial of service attacks."); -static cvar_t sv_maxservers = CVARD("sv_maxservers", "1000", "Limits the number of servers (total from all games) that may be known. This is to reduce denial of service attacks."); +static cvar_t sv_maxservers = CVARD("sv_maxservers", "10000", "Limits the number of servers (total from all games) that may be known. This is to reduce denial of service attacks."); +static cvar_t sv_hideinactivegames = CVARD("sv_hideinactivegames", "1", "Don't show known games that currently have no servers in html listings."); +static cvar_t sv_sortlist = CVARD("sv_sortlist", "3", "Controls sorting of the http output:\n0: don't bother\n1: clients then address\n2: hostname then address\n3: clients then hostname then address\n4: just address"); +static cvar_t sv_hostname = CVARD("hostname", "Unnamed FTE-Master", "Controls sorting of the http output:\n0: don't bother\n1: clients then address\n2: hostname then address\n3: clients then hostname then address\n4: just address"); +static char *master_css; static unsigned int SVM_GenerateBrokerKey(const char *brokerid) { @@ -148,35 +158,54 @@ static svm_game_t *SVM_FindGame(const char *game, int create) { svm_game_t *g, **link; const char *sanitise; + const char *a; for (g = svm.firstgame; g; g = g->next) { if (!Q_strcasecmp(game, g->name)) return g; + + if (g->aliases) + { + for (a = g->aliases; *a; a+=strlen(a)+1) + { + if (!Q_strcasecmp(game, a)) + return g; + } + } } if (create) { - if (svm.numgames >= sv_maxgames.ival) + if (create != 2) { - Con_DPrintf("game limit exceeded\n"); - return NULL; - } - //block some chars that may cause issues/exploits. sorry. - for (sanitise = game; *sanitise; sanitise++) - { - if ((*sanitise >= 'a' && *sanitise <= 'z') || //allow lowercase - (*sanitise >= 'A' && *sanitise <= 'Z') || //allow uppercase - (*sanitise >= '0' && *sanitise <= '9') || //allow numbers (but not leeding, see below) - (*sanitise == '-' || *sanitise == '_')) //allow a little punctuation, to make up for the lack of spaces. - continue; - return NULL; + if (svm.numgames >= sv_maxgames.ival) + { + Con_DPrintf("game limit exceeded\n"); + return NULL; + } + //block some chars that may cause issues/exploits. sorry. + for (sanitise = game; *sanitise; sanitise++) + { + if ((*sanitise >= 'a' && *sanitise <= 'z') || //allow lowercase + (*sanitise >= 'A' && *sanitise <= 'Z') || //allow uppercase + (*sanitise >= '0' && *sanitise <= '9') || //allow numbers (but not leeding, see below) + (*sanitise == '-' || *sanitise == '_')) //allow a little punctuation, to make up for the lack of spaces. + continue; + return NULL; + } } if (!*game || (*game >= '0' && *game <= '9')) return NULL; //must not start with a number either. g = ZF_Malloc(sizeof(*g) + strlen(game)); if (g) { + char *n; strcpy(g->name, game); + for (n = g->name; *n; n++) + { //some extra fixups, because formalnames are messy. + if (*n == ' ' || *n == '\t' || *n == ':' || *n == '?' || *n == '#' || *n == '.') + *n = '_'; + } g->persistent = create==2; g->next = NULL; @@ -191,6 +220,62 @@ static svm_game_t *SVM_FindGame(const char *game, int create) return g; } +static int QDECL SVM_SortOrder(const void *v1, const void *v2) +{ + svm_server_t const*const s1 = *(svm_server_t const*const*const)v1; + svm_server_t const*const s2 = *(svm_server_t const*const*const)v2; + int t, i; + if (sv_sortlist.ival&8) + return s1->expiretime > s2->expiretime; + + if (sv_sortlist.ival&1) + if ((t=(s2->clients-s1->clients))) + return (t>0)?1:-1; + if (sv_sortlist.ival&2) + if ((t=strcmp(s1->hostname, s2->hostname))) + return (t>0)?1:-1; + + //sort by scheme, address family, and ip + if ((t=(s1->adr.prot-s2->adr.prot))) + return (t>0)?1:-1; + if ((t=(s1->adr.type-s2->adr.type))) + return (t>0)?1:-1; + if (s1->adr.type==NA_IP) + i = sizeof(s1->adr.address.ip); + else if (s1->adr.type==NA_IPV6) + i = sizeof(s1->adr.address.ip6); + else i = 0; + for(t = 0; t < i; t++) + if (s1->adr.address.ip6[i] != s2->adr.address.ip6[i]) + return (s2->adr.address.ip6[i]>s1->adr.address.ip6[i])?1:-1; + + //and now do port numbers too. + t = BigShort(s1->adr.port) - BigShort(s2->adr.port); + if (t) + return (t>0)?1:-1; + return 0; +} + +static void SVM_SortServers(svm_game_t *game) +{ + svm_server_t **serverlink, *s; + svm_server_t **sv = malloc(sizeof(*sv)*game->numservers); + int i; + if (!sv_sortlist.ival) + return; + + for (i=0, s = game->firstserver; s; s = s->next) + sv[i++] = s; + qsort(sv, i, sizeof(*sv), SVM_SortOrder); + + for (i = 0, serverlink = &game->firstserver; i < game->numservers; i++) + { + *serverlink = sv[i]; + serverlink = &sv[i]->next; + } + *serverlink = NULL; +} + static void SVM_RemoveOldServers(void) { svm_game_t **gamelink, *g; @@ -289,7 +374,7 @@ int SVM_AddIPAddresses(sizebuf_t *sb, int first, int ver, const char *gamename, return number; } -static char *QuakeCharsToHTML(char *outhtml, size_t outsize, const char *quake) +static char *QuakeCharsToHTML(char *outhtml, size_t outsize, const char *quake, qboolean deunderscore) { char *ret = outhtml; conchar_t chars[8192], *c=chars, *end; @@ -371,6 +456,8 @@ static char *QuakeCharsToHTML(char *outhtml, size_t outsize, const char *quake) Q_strncpyz(outhtml, "'", outsize); b=strlen(outhtml); } + else if (codepoint == '_' && deunderscore) + *outhtml = ' ', b =1; else b = utf8_encode(outhtml, codepoint, outsize); if (b > 0) @@ -383,10 +470,12 @@ static char *QuakeCharsToHTML(char *outhtml, size_t outsize, const char *quake) return ret; } -vfsfile_t *SVM_GenerateIndex(const char *requesthost, const char *fname) +static void SVM_Init(void) { - static const char *thecss = - ""; + "" + ); +} + +vfsfile_t *SVM_GenerateIndex(const char *requesthost, const char *fname) +{ char tmpbuf[256]; char hostname[1024]; const char *url; @@ -415,19 +509,24 @@ vfsfile_t *SVM_GenerateIndex(const char *requesthost, const char *fname) svm_server_t *server; vfsfile_t *f = NULL; unsigned clients = 0, maxclients=0, totalclients=0; + if (!master_css) + SVM_Init(); if (!strcmp(fname, "index.html")) { f = VFSPIPE_Open(1, false); - VFS_PRINTF(f, "%s", thecss); - VFS_PRINTF(f, "

FTE-Master

\n"); + VFS_PRINTF(f, "%s", master_css); + VFS_PRINTF(f, "

%s

\n", sv_hostname.string); VFS_PRINTF(f, "\n"); VFS_PRINTF(f, "\n"); for (game = svm.firstgame; game; game = game->next) { for (clients=0, server = game->firstserver; server; server = server->next) clients += server->clients; - if (game->numservers) //only show active servers - VFS_PRINTF(f, "\n", game->name, game->name, clients, (unsigned)game->numservers); + if (game->numservers || !sv_hideinactivegames.ival) //only show active servers + { + QuakeCharsToHTML(tmpbuf, sizeof(tmpbuf), game->name, true); + VFS_PRINTF(f, "\n", game->name, tmpbuf, clients, (unsigned)game->numservers); + } totalclients += clients; } VFS_PRINTF(f, "
Active GamesPlayersServer Count
%s%u player(s)%u server(s)
%s%u player(s)%u server(s)
\n"); @@ -439,7 +538,7 @@ vfsfile_t *SVM_GenerateIndex(const char *requesthost, const char *fname) int count; f = VFSPIPE_Open(1, false); - VFS_PRINTF(f, "%s", thecss); + VFS_PRINTF(f, "%s", master_css); VFS_PRINTF(f, "

Single Server Info

\n"); VFS_PRINTF(f, "\n"); @@ -450,7 +549,10 @@ vfsfile_t *SVM_GenerateIndex(const char *requesthost, const char *fname) { server = SVM_GetServer(&adr[count]); if (server) - VFS_PRINTF(f, "\n", server->game?server->game->name:"Unknown", NET_AdrToString(tmpbuf, sizeof(tmpbuf), &server->adr), server->hostname, server->gamedir, server->mapname, server->clients, server->maxclients); + { + QuakeCharsToHTML(hostname, sizeof(hostname), server->hostname, false); + VFS_PRINTF(f, "\n", server->game?server->game->name:"Unknown", NET_AdrToString(tmpbuf, sizeof(tmpbuf), &server->adr), server->needpass?"🔒":"", hostname, server->gamedir, server->mapname, server->clients, server->maxclients); + } else VFS_PRINTF(f, "\n", NET_AdrToString(tmpbuf, sizeof(tmpbuf), &adr[count])); } @@ -462,11 +564,17 @@ vfsfile_t *SVM_GenerateIndex(const char *requesthost, const char *fname) game = SVM_FindGame(gamename, false); f = VFSPIPE_Open(1, false); - VFS_PRINTF(f, "%s", thecss); - VFS_PRINTF(f, "

Servers for %s

\n", gamename); + VFS_PRINTF(f, "%s", master_css); + + if (!strcmp(gamename, "UNKNOWN")) + VFS_PRINTF(f, "

Firewalled/NATed/Unresponsive/Congested (Misconfigured) Servers

\n"); + else + VFS_PRINTF(f, "

Servers for %s

\n", QuakeCharsToHTML(tmpbuf, sizeof(tmpbuf), gamename, true)); if(game) { + SVM_SortServers(game); + VFS_PRINTF(f, "
%s%s%s%s%s%u/%u
%s%s%s%s%s%s%u/%u
?%s????/?
\n"); VFS_PRINTF(f, "\n"); for (server = game->firstserver; server; server = server->next) @@ -478,8 +586,8 @@ vfsfile_t *SVM_GenerateIndex(const char *requesthost, const char *fname) } else url = NET_AdrToString(tmpbuf, sizeof(tmpbuf), &server->adr); - QuakeCharsToHTML(hostname, sizeof(hostname), server->hostname); - VFS_PRINTF(f, "\n", url, hostname, server->gamedir, server->mapname, server->clients, server->maxclients); + QuakeCharsToHTML(hostname, sizeof(hostname), server->hostname, false); + VFS_PRINTF(f, "\n", url, server->needpass?"🔒":"", hostname, server->gamedir, server->mapname, server->clients, server->maxclients); clients += server->clients; maxclients += server->maxclients; } @@ -499,7 +607,7 @@ vfsfile_t *SVM_GenerateIndex(const char *requesthost, const char *fname) for (server = (game?game->firstserver:NULL); server; server = server->next) { if (server->brokerid) - VFS_PRINTF(f, "rtc:///%s \\maxclients\\%u\\clients\\%u\\hostname\\%s\\modname\\%s\\mapname\\%s\n", server->brokerid, server->maxclients, server->clients, server->hostname, server->gamedir, server->mapname); + VFS_PRINTF(f, "rtc:///%s \\maxclients\\%u\\clients\\%u\\hostname\\%s\\modname\\%s\\mapname\\%s%s\n", server->brokerid, server->maxclients, server->clients, server->hostname, server->gamedir, server->mapname, server->needpass?"\\needpass\\1":""); else VFS_PRINTF(f, "%s\n", NET_AdrToString(tmpbuf, sizeof(tmpbuf), &server->adr)); } @@ -555,13 +663,14 @@ void SVM_RemoveBrokerGame(const char *brokerid) if (s->brokerid == brokerid) { *link = s->next; + Hash_RemoveDataKey(&svm.serverhash, SVM_GenerateBrokerKey(brokerid), s); Z_Free(s); game->numservers--; svm.numservers--; return; } else - link = &(*link)->next; + link = &s->next; } Con_Printf("SVM_RemoveBrokerGame: failed to remove brokered server: %s\n", brokerid); @@ -608,9 +717,23 @@ void SVM_AddBrokerGame(const char *brokerid, const char *info) static svm_server_t *SVM_Heartbeat(const char *gamename, netadr_t *adr, int numclients, float validuntil) { svm_server_t *server = SVM_GetServer(adr); - svm_game_t *game = SVM_FindGame(gamename, true); - if (!game) - return NULL; + svm_game_t *game; + + if (!gamename) + { //no gamename is a placeholder server, to say that there's a server there but it isn't responding to our getinfos... (ie: to list misconfigured servers too) + if (server) + { //it still exists, renew it, but don't otherwise care too much. + server->expiretime = validuntil; + return NULL; + } + game = SVM_FindGame("UNKNOWN", true); + } + else + { + game = SVM_FindGame(gamename, true); + if (!game) + return NULL; + } if (server && server->game != game) { @@ -664,10 +787,22 @@ void tobase64(unsigned char *out, int outlen, unsigned char *in, int inlen); void SVM_GenChallenge(char *out, size_t outsize, netadr_t *foradr) { //this function needs to return some sort of unguessable string so that you can't spoof the server with fake responses char adr[64]; - const unsigned char *strings[] = {"somethingrandom", (const unsigned char *)NET_AdrToString(adr, sizeof(adr), foradr)}; - size_t lengths[] = {strlen(strings[0]), strlen(strings[1])}; + static char randumb[16]; + const unsigned char *strings[] = {randumb, (const unsigned char *)NET_AdrToString(adr, sizeof(adr), foradr)}; + size_t lengths[] = {sizeof(randumb)-1, strlen(strings[1])}; char digest[4*5]; - int digestsize = SHA1_m(digest, sizeof(digest), countof(lengths), strings, lengths); + int digestsize; + + if (!*randumb) + { + int i; + srand(time(NULL)); //lame + for (i = 0; i < sizeof(randumb)-1; i++) + while (!randumb[i]) + randumb[i] = rand(); + } + + digestsize = SHA1_m(digest, sizeof(digest), countof(lengths), strings, lengths); #ifdef TCPCONNECT tobase64(out, outsize, digest, min(16, digestsize)); //truncate it, so its not excessive @@ -677,212 +812,295 @@ void SVM_GenChallenge(char *out, size_t outsize, netadr_t *foradr) #endif } -void SVM_Think(int port) +//switch net_from's reported connection, so we reply from a different udp socket from the one a packet was received from. +static void SVM_SwitchQuerySocket(void) { - char *s; - int cookie = 0; - int giveup = 500; - - while (giveup-- > 0 && (cookie=NET_GetPacket (svm_sockets, cookie)) >= 0) + size_t c; + //switch the info query to our other udp socket, so any firewall/nat over the server blocks it. + //this is to prevent people from thinking that the server is actually accessible. + for (c = 0; c < countof(svm_sockets->conn); c++) { - net_message.data[net_message.cursize] = '\0'; //null term all strings. + if (!svm_sockets->conn[c]) + continue; //that one's dead, jim + if (c+1 == net_from.connum) + continue; //ignore this one, its the one we received the packet from + //make sure its a datagram connection, and not some tcp weirdness. + if (svm_sockets->conn[c]->prot == NP_DGRAM && + (svm_sockets->conn[c]->addrtype[0] == net_from.type || svm_sockets->conn[c]->addrtype[1] == net_from.type)) + { //okay, looks like we should be able to respond on this one. lets see if their firewall stops us from finding out more about them. + net_from.connum = c+1; + break; + } + } +} - //well that's annoying. why is our networking code not doing this? LAME! - if (net_from.type == NA_IPV6 && - !*(int*)&net_from.address.ip6[0] && - !*(int*)&net_from.address.ip6[4] && - !*(short*)&net_from.address.ip6[8] && - *(short*)&net_from.address.ip6[10]==(short)0xffff) - { //convert this ipv4-mapped-ipv6 address back to actual ipv4, so we don't get confused about stuff. - net_from.type = NA_IP; - *(int*)&net_from.address.ip[0] = *(int*)&net_from.address.ip6[12]; - //and null it out, just in case. - *(int*)&net_from.address.ip6[8]=0; - *(int*)&net_from.address.ip6[12]=0; - } +static void SVM_ProcessUDPPacket(void) +{ + char *s, *line; - if (NET_WasSpecialPacket(svm_sockets)) - continue; - - svm.time = Sys_DoubleTime(); - - MSG_BeginReading(msg_nullnetprim); - if (MSG_ReadLong() != -1 || msg_badread) - { //go back to start... - MSG_BeginReading(msg_nullnetprim); - } - s = MSG_ReadStringLine(); - s = COM_Parse(s); - if (!strcmp(com_token, "getservers") || !strcmp(com_token, "getserversExt")) - { //q3 - sizebuf_t sb; - int ver; - char *eos; - char game[64]; - qboolean ext = !strcmp(com_token, "getserversExt"); - const char *resp=ext?"getserversExtResponse":"getserversResponse"; - qboolean empty = false; - qboolean full = false; - qboolean ipv4 = !ext; - qboolean ipv6 = false; - int gametype = -1; - s = COM_ParseOut(s, game, sizeof(game)); - ver = strtol(game, &eos, 0); - if (*eos) - { //not a number, must have been a game name. - s = COM_Parse(s); - ver = strtol(com_token, NULL, 0); - } - else //first arg was a number. that means its vanilla quake3. - Q_strncpyz(game, "Quake3", sizeof(game)); - for(;s&&*s;) - { - s = COM_Parse(s); - if (!strcmp(com_token, "empty")) - empty = true; - else if (!strcmp(com_token, "full")) - full = true; - - else if (!strcmp(com_token, "ipv4")) - ipv4 = true; - else if (!strcmp(com_token, "ipv6")) - ipv6 = true; - - else if (!strcmp(com_token, "ffa")) - gametype = GT_FFA; - else if (!strcmp(com_token, "tourney")) - gametype = GT_TOURNEY; - else if (!strcmp(com_token, "team")) - gametype = GT_TEAM; - else if (!strcmp(com_token, "ctf")) - gametype = GT_CTF; - else if (!strncmp(com_token, "gametype=", 9)) - gametype = atoi(com_token+9); - else - { - char buf[256]; - Con_DPrintf("Unknown request filter: %s\n", COM_QuotedString(com_token, buf, sizeof(buf), false)); - } - } - if (!ipv4 && !ipv6) - ipv4 = ipv6 = true; //neither specified? use both - - - svm.total.queries++; - memset(&sb, 0, sizeof(sb)); - sb.maxsize = sizeof(net_message_buffer)-2; - sb.data = net_message_buffer; - MSG_WriteLong(&sb, -1); - SZ_Write(&sb, resp, strlen(resp)); //WriteString, but without the null. - SVM_AddIPAddresses(&sb, 0, ver, game, ipv4, ipv6, empty, full, true, gametype); - sb.maxsize+=2; - MSG_WriteByte(&sb, '\\'); //otherwise the last may be considered invalid and ignored. - MSG_WriteByte(&sb, 'E'); - MSG_WriteByte(&sb, 'O'); - MSG_WriteByte(&sb, 'T'); - NET_SendPacket(svm_sockets, sb.cursize, sb.data, &net_from); - } - else if (!strcmp(com_token, "heartbeat")) - { //quake2 heartbeat. Serverinfo and players should follow. - if (*s == '\n' && s[1] == '\\') - { //there's some serverinfo there, must be q2... - svm.total.heartbeats++; - SVM_Heartbeat("Quake2", &net_from, 0, svm.time + sv_heartbeattimeout.ival); - } - else - { //dp/q3/etc are annoying, but we can query from an emphemerial socket to check NAT rules. - sizebuf_t sb; - char ourchallenge[256]; - SVM_GenChallenge(ourchallenge, sizeof(ourchallenge), &net_from); - svm.total.queries++; - memset(&sb, 0, sizeof(sb)); - sb.maxsize = sizeof(net_message_buffer); - sb.data = net_message_buffer; - MSG_WriteLong(&sb, -1); - MSG_WriteString(&sb, va("getinfo %s\n", ourchallenge)); - sb.cursize--; - NET_SendPacket(svm_sockets, sb.cursize, sb.data, &net_from); - } - } - else if (!strcmp(com_token, "infoResponse")) - { - char ourchallenge[256]; - int clients; - const char *game, *chal; - svm_server_t *srv; - s = MSG_ReadStringLine(); - svm.total.heartbeats++; - chal = Info_ValueForKey(s, "challenge"); - SVM_GenChallenge(ourchallenge, sizeof(ourchallenge), &net_from); - if (!strcmp(chal, ourchallenge)) - { - clients = atoi(Info_ValueForKey(s, "clients")); - game = Info_ValueForKey(s, "gamename"); - srv = SVM_Heartbeat(game, &net_from, clients, svm.time + sv_heartbeattimeout.ival); - if (srv) - { - if (developer.ival) - Info_Print(s, "\t"); - srv->protover = atoi(Info_ValueForKey(s, "protocol")); - srv->maxclients = atoi(Info_ValueForKey(s, "sv_maxclients")); - Q_strncpyz(srv->hostname, Info_ValueForKey(s, "hostname"), sizeof(srv->hostname)); - Q_strncpyz(srv->gamedir, Info_ValueForKey(s, "modname"), sizeof(srv->gamedir)); - Q_strncpyz(srv->mapname, Info_ValueForKey(s, "mapname"), sizeof(srv->mapname)); - } - } - } - else if (!strcmp(com_token, "query")) - { //quake2 server listing request - sizebuf_t sb; - svm.total.queries++; - memset(&sb, 0, sizeof(sb)); - sb.maxsize = sizeof(net_message_buffer); - sb.data = net_message_buffer; - MSG_WriteLong(&sb, -1); - MSG_WriteString(&sb, "servers\n"); - sb.cursize--; - SVM_AddIPAddresses(&sb, 0, 0, "Quake2", true, false, true, true, false, -1); - NET_SendPacket(svm_sockets, sb.cursize, sb.data, &net_from); - } - else if (*com_token == S2M_HEARTBEAT) //sequence, players - { //quakeworld heartbeat - int players; - s = MSG_ReadStringLine(); - //sequence = atoi(s); - s = MSG_ReadStringLine(); - players = atoi(s); - svm.total.heartbeats++; - SVM_Heartbeat("QuakeWorld", &net_from, players, svm.time + sv_heartbeattimeout.ival); - } - else if (*com_token == C2M_MASTER_REQUEST) - { //quakeworld server listing request - sizebuf_t sb; - svm.total.queries++; - memset(&sb, 0, sizeof(sb)); - sb.maxsize = sizeof(net_message_buffer); - sb.data = net_message_buffer; - MSG_WriteLong(&sb, -1); - MSG_WriteByte(&sb, M2C_MASTER_REPLY); - MSG_WriteByte(&sb, '\n'); - SVM_AddIPAddresses(&sb, 0, 0, "QuakeWorld", true, false, true, true, false, -1); - NET_SendPacket(svm_sockets, sb.cursize, sb.data, &net_from); - } - else if (*com_token == A2A_PING) - { //quakeworld server listing request - sizebuf_t sb; - svm.total.queries++; - memset(&sb, 0, sizeof(sb)); - sb.maxsize = sizeof(net_message_buffer); - sb.data = net_message_buffer; - MSG_WriteLong(&sb, -1); - MSG_WriteByte(&sb, A2A_ACK); - MSG_WriteByte(&sb, '\n'); - NET_SendPacket(svm_sockets, sb.cursize, sb.data, &net_from); - } - else - svm.total.junk++; + //we shouldn't be taking anything else... + if (net_from.prot != NP_DGRAM) + { + Con_DPrintf("master: ignoring non-datagram message\n"); + return; } + net_message.data[net_message.cursize] = '\0'; //null term all strings. + + //well that's annoying. why is our networking code not doing this? LAME! + if (net_from.type == NA_IPV6 && + !*(int*)&net_from.address.ip6[0] && + !*(int*)&net_from.address.ip6[4] && + !*(short*)&net_from.address.ip6[8] && + *(short*)&net_from.address.ip6[10]==(short)0xffff) + { //convert this ipv4-mapped-ipv6 address back to actual ipv4, so we don't get confused about stuff. + net_from.type = NA_IP; + *(int*)&net_from.address.ip[0] = *(int*)&net_from.address.ip6[12]; + //and null it out, just in case. + *(int*)&net_from.address.ip6[8]=0; + *(int*)&net_from.address.ip6[12]=0; + } + + if (NET_WasSpecialPacket(svm_sockets)) + { + Con_DPrintf("master: ignoring special packet\n"); + return; + } + + svm.time = Sys_DoubleTime(); + + MSG_BeginReading(msg_nullnetprim); + if (MSG_ReadLong() != -1 || msg_badread) + { //go back to start... + MSG_BeginReading(msg_nullnetprim); + } + line = MSG_ReadStringLine(); + s = COM_Parse(line); + if (!strcmp(com_token, "getservers") || !strcmp(com_token, "getserversExt")) + { //q3 + sizebuf_t sb; + int ver; + char *eos; + char game[64]; + qboolean ext = !strcmp(com_token, "getserversExt"); + const char *resp=ext?"getserversExtResponse":"getserversResponse"; + qboolean empty = false; + qboolean full = false; + qboolean ipv4 = !ext; + qboolean ipv6 = false; + int gametype = -1; + s = COM_ParseOut(s, game, sizeof(game)); + ver = strtol(game, &eos, 0); + if (*eos) + { //not a number, must have been a game name. + s = COM_Parse(s); + ver = strtol(com_token, NULL, 0); + } + else //first arg was a number. that means its vanilla quake3. + Q_strncpyz(game, QUAKE3PROTOCOLNAME, sizeof(game)); + for(;s&&*s;) + { + s = COM_Parse(s); + if (!strcmp(com_token, "empty")) + empty = true; + else if (!strcmp(com_token, "full")) + full = true; + + else if (!strcmp(com_token, "ipv4")) + ipv4 = true; + else if (!strcmp(com_token, "ipv6")) + ipv6 = true; + + else if (!strcmp(com_token, "ffa")) + gametype = GT_FFA; + else if (!strcmp(com_token, "tourney")) + gametype = GT_TOURNEY; + else if (!strcmp(com_token, "team")) + gametype = GT_TEAM; + else if (!strcmp(com_token, "ctf")) + gametype = GT_CTF; + else if (!strncmp(com_token, "gametype=", 9)) + gametype = atoi(com_token+9); + else + { + char buf[256]; + Con_DPrintf("Unknown request filter: %s\n", COM_QuotedString(com_token, buf, sizeof(buf), false)); + } + } + if (!ipv4 && !ipv6) + ipv4 = ipv6 = true; //neither specified? use both + + + svm.total.queries++; + memset(&sb, 0, sizeof(sb)); + sb.maxsize = sizeof(net_message_buffer)-2; + sb.data = net_message_buffer; + MSG_WriteLong(&sb, -1); + SZ_Write(&sb, resp, strlen(resp)); //WriteString, but without the null. + SVM_AddIPAddresses(&sb, 0, ver, game, ipv4, ipv6, empty, full, true, gametype); + sb.maxsize+=2; + MSG_WriteByte(&sb, '\\'); //otherwise the last may be considered invalid and ignored. + MSG_WriteByte(&sb, 'E'); + MSG_WriteByte(&sb, 'O'); + MSG_WriteByte(&sb, 'T'); + NET_SendPacket(svm_sockets, sb.cursize, sb.data, &net_from); + } + else if (!strcmp(com_token, "heartbeat")) + { //quake2 heartbeat. Serverinfo and players should follow. + if (*s == '\n' && s[1] == '\\') + { //there's some serverinfo there, must be q2... + svm.total.heartbeats++; + SVM_Heartbeat(QUAKE2PROTOCOLNAME, &net_from, 0, svm.time + sv_heartbeattimeout.ival); + } + else + { //dp/q3/etc are annoying, but we can query from an emphemerial socket to check NAT rules. + sizebuf_t sb; + char ourchallenge[256]; + SVM_GenChallenge(ourchallenge, sizeof(ourchallenge), &net_from); + svm.total.queries++; + + //placeholder listing... + SVM_Heartbeat(NULL, &net_from, 0, svm.time + sv_heartbeattimeout.ival); + SVM_SwitchQuerySocket(); + + memset(&sb, 0, sizeof(sb)); + sb.maxsize = sizeof(net_message_buffer); + sb.data = net_message_buffer; + MSG_WriteLong(&sb, -1); + MSG_WriteString(&sb, va("getinfo %s\n", ourchallenge)); + sb.cursize--; + NET_SendPacket(svm_sockets, sb.cursize, sb.data, &net_from); + } + } + else if (!strcmp(com_token, "infoResponse")) + { + char ourchallenge[256]; + int clients; + const char *game, *chal; + svm_server_t *srv; + s = MSG_ReadStringLine(); + svm.total.heartbeats++; + chal = Info_ValueForKey(s, "challenge"); + SVM_GenChallenge(ourchallenge, sizeof(ourchallenge), &net_from); + if (!strcmp(chal, ourchallenge)) + { + clients = atoi(Info_ValueForKey(s, "clients")); + game = Info_ValueForKey(s, "gamename"); + if (!*game) + game = QUAKE3PROTOCOLNAME; + srv = SVM_Heartbeat(game, &net_from, clients, svm.time + sv_heartbeattimeout.ival); + if (srv) + { + if (developer.ival) + Info_Print(s, "\t"); + srv->protover = atoi(Info_ValueForKey(s, "protocol")); + srv->maxclients = atoi(Info_ValueForKey(s, "sv_maxclients")); + srv->needpass = !!atoi(Info_ValueForKey(s, "needpass")); + Q_strncpyz(srv->hostname, Info_ValueForKey(s, "hostname"), sizeof(srv->hostname)); + Q_strncpyz(srv->gamedir, Info_ValueForKey(s, "modname"), sizeof(srv->gamedir)); + Q_strncpyz(srv->mapname, Info_ValueForKey(s, "mapname"), sizeof(srv->mapname)); + } + } + } + else if (!strcmp(com_token, "query")) + { //quake2 server listing request + sizebuf_t sb; + svm.total.queries++; + memset(&sb, 0, sizeof(sb)); + sb.maxsize = sizeof(net_message_buffer); + sb.data = net_message_buffer; + MSG_WriteLong(&sb, -1); + MSG_WriteString(&sb, "servers\n"); + sb.cursize--; + SVM_AddIPAddresses(&sb, 0, 0, QUAKE2PROTOCOLNAME, true, false, true, true, false, -1); + NET_SendPacket(svm_sockets, sb.cursize, sb.data, &net_from); + } + else if (*com_token == S2M_HEARTBEAT) //sequence, players + { //quakeworld heartbeat + int players; + sizebuf_t sb; + s = MSG_ReadStringLine(); + //sequence = atoi(s); + s = MSG_ReadStringLine(); + players = atoi(s); + svm.total.heartbeats++; + + + //placeholder listing... + SVM_Heartbeat(NULL, &net_from, players, svm.time + sv_heartbeattimeout.ival); + SVM_SwitchQuerySocket(); + + //send it a proper query. We'll fill in the other details on response. + memset(&sb, 0, sizeof(sb)); + sb.maxsize = sizeof(net_message_buffer); + sb.data = net_message_buffer; + MSG_WriteLong(&sb, -1); + MSG_WriteString(&sb, va("status %i\n", 1)); + sb.cursize--; + NET_SendPacket(svm_sockets, sb.cursize, sb.data, &net_from); + } + else if (*com_token == A2C_PRINT) + { //quakeworld response from 'status' requests, providing for actual info (and so that we know its reachable from other addresses) + //there's no challenge, these could easily be spoofed. :( + int clients; + const char *game; + svm_server_t *srv; + s = ++line; + + clients = atoi(Info_ValueForKey(s, "clients")); + game = Info_ValueForKey(s, "gamename"); + if (!*game) + game = QUAKEWORLDPROTOCOLNAME; + srv = SVM_Heartbeat(game, &net_from, clients, svm.time + sv_heartbeattimeout.ival); + if (srv) + { + if (developer.ival) + Info_Print(s, "\t"); + srv->protover = 3;//atoi(Info_ValueForKey(s, "protocol")); + srv->maxclients = atoi(Info_ValueForKey(s, "maxclients")); + srv->needpass = !!atoi(Info_ValueForKey(s, "needpass")); + Q_strncpyz(srv->hostname, Info_ValueForKey(s, "hostname"), sizeof(srv->hostname)); + Q_strncpyz(srv->gamedir, Info_ValueForKey(s, "*gamedir"), sizeof(srv->gamedir)); + Q_strncpyz(srv->mapname, Info_ValueForKey(s, "map"), sizeof(srv->mapname)); + } + } + else if (*com_token == C2M_MASTER_REQUEST) + { //quakeworld server listing request + sizebuf_t sb; + svm.total.queries++; + memset(&sb, 0, sizeof(sb)); + sb.maxsize = sizeof(net_message_buffer); + sb.data = net_message_buffer; + MSG_WriteLong(&sb, -1); + MSG_WriteByte(&sb, M2C_MASTER_REPLY); + MSG_WriteByte(&sb, '\n'); + SVM_AddIPAddresses(&sb, 0, 0, QUAKEWORLDPROTOCOLNAME, true, false, true, true, false, -1); + NET_SendPacket(svm_sockets, sb.cursize, sb.data, &net_from); + } + else if (*com_token == A2A_PING) + { //quakeworld server ping request... because we can. + sizebuf_t sb; + svm.total.queries++; + memset(&sb, 0, sizeof(sb)); + sb.maxsize = sizeof(net_message_buffer); + sb.data = net_message_buffer; + MSG_WriteLong(&sb, -1); + MSG_WriteByte(&sb, A2A_ACK); + MSG_WriteByte(&sb, '\n'); + NET_SendPacket(svm_sockets, sb.cursize, sb.data, &net_from); + } + else if (*com_token == S2M_SHUTDOWN) + { //quakeworld server shutting down... + //this isn't actually useful. we can't use it because we can't protect against spoofed denial-of-service attacks. + //we could only use this by sending it a few pings to see if it is actually still responding. which is unreliable (especially if we're getting spammed by packet floods). + } + else + svm.total.junk++; +} + +void SVM_Think(int port) +{ + NET_ReadPackets (svm_sockets); SVM_RemoveOldServers(); } #else @@ -928,10 +1146,73 @@ static void SVM_Status_f(void) Con_Printf("Queries/min: %f\n", (s1->queries-s2->queries)/period); } +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 + while (*g) + { + g = COM_Parse(g); + SVM_RegisterAlias(game, com_token); + } + } + + return false; +} + +static void SVM_GameAlias_f(void) +{ + svm_game_t *game = SVM_FindGame(Cmd_Argv(1), 2); + if (!game) + { + Con_Printf("Unable to register game %s\n", Cmd_Argv(1)); + return; + } + SVM_RegisterAlias(game, Cmd_Argv(2)); +} void SV_Init (struct quakeparms_s *parms) { int manarg; - char *g; COM_InitArgv (parms->argc, parms->argv); @@ -957,8 +1238,9 @@ void SV_Init (struct quakeparms_s *parms) Cmd_AddCommand ("quit", SV_Quit_f); Cmd_AddCommand ("status", SVM_Status_f); + Cmd_AddCommand ("gamealias", SVM_GameAlias_f); - svm_sockets = FTENET_CreateCollection(true); + svm_sockets = FTENET_CreateCollection(true, SVM_ProcessUDPPacket); Hash_InitTable(&svm.serverhash, 1024, Z_Malloc(Hash_BytesForBuckets(1024))); Cvar_Register(&sv_masterport, "server control variables"); @@ -966,6 +1248,9 @@ void SV_Init (struct quakeparms_s *parms) 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_ParseWatches(); host_initialized = true; @@ -987,12 +1272,8 @@ void SV_Init (struct quakeparms_s *parms) Cvar_ForceCallback(&sv_masterport); Cvar_ForceCallback(&sv_masterport_tcp); - if (fs_manifest->protocolname) - for (g = fs_manifest->protocolname; *g; ) - { - g = COM_Parse(g); - SVM_FindGame(com_token, 2); - } + SVM_FoundManifest(NULL, fs_manifest); + FS_EnumerateKnownGames(SVM_FoundManifest, NULL); Con_Printf ("Exe: %s\n", version_string()); diff --git a/engine/server/sv_mvd.c b/engine/server/sv_mvd.c index 422a8c251..6bba9049b 100644 --- a/engine/server/sv_mvd.c +++ b/engine/server/sv_mvd.c @@ -85,6 +85,8 @@ int demomsgtype; int demomsgto; static char demomsgbuf[MAX_OVERALLMSGLEN]; +static void SV_MVD_Stopped(void); + static mvddest_t *singledest; //used when a stream is starting up so redundant data doesn't get dumped into other streams static struct reversedest_s { @@ -557,7 +559,8 @@ hashedpassword: { if (p->hasauthed == true) { - SV_MVD_Record(SV_MVD_InitStream(clientstream, userinfo)); + if (!SV_MVD_Record(SV_MVD_InitStream(clientstream, userinfo))) + return QTV_ERROR; return QTV_ACCEPT; } } @@ -573,7 +576,8 @@ hashedpassword: e = NULL; dst = SV_MVD_InitStream(clientstream, userinfo); dst->droponmapchange = p->isreverse; - SV_MVD_Record(dst); + if (!SV_MVD_Record(dst)) + return QTV_ERROR; return QTV_ACCEPT; } else @@ -1531,7 +1535,7 @@ void SV_MVDStop (enum mvdclosereason_e reason, qboolean mvdonly) // stop and remove if (!demo.dest) - sv.mvdrecording = false; + SV_MVD_Stopped(); if (reason == MVD_CLOSE_DISCONNECTED) SV_BroadcastPrintf (PRINT_CHAT, "QTV disconnected\n"); @@ -1554,7 +1558,7 @@ void SV_MVDStop (enum mvdclosereason_e reason, qboolean mvdonly) DestCloseAllFlush(reason, mvdonly); if (!demo.dest) //might still be streaming qtv. - sv.mvdrecording = false; + SV_MVD_Stopped(); Cvar_ForceSet(Cvar_Get("serverdemo", "", CVAR_NOSET, ""), ""); } @@ -1709,6 +1713,17 @@ qboolean SV_MVD_Record (mvddest_t *dest) return true; } +static void SV_MVD_Stopped(void) +{ //all recording has stopped. clean up any demo.recorder state + if (demo.recorder.frameunion.frames) + { + Z_Free(demo.recorder.frameunion.frames); + demo.recorder.frameunion.frames = NULL; + } + sv.mvdrecording = false; + memset(&demo, 0, sizeof(demo)); +} + void SV_EnableClientsCSQC(void); void SV_MVD_SendInitialGamestate(mvddest_t *dest) { diff --git a/engine/server/sv_sys_unix.c b/engine/server/sv_sys_unix.c index 99f5d586e..6e822f469 100644 --- a/engine/server/sv_sys_unix.c +++ b/engine/server/sv_sys_unix.c @@ -63,9 +63,10 @@ cvar_t sys_extrasleep = CVAR("sys_extrasleep","0"); cvar_t sys_colorconsole = CVAR("sys_colorconsole", "1"); cvar_t sys_linebuffer = CVARC("sys_linebuffer", "1", Sys_Linebuffer_Callback); -qboolean stdin_ready; +static qboolean stdin_ready; +static qboolean noconinput = false; -struct termios orig, changes; +static struct termios orig, changes; /* =============================================================================== @@ -216,7 +217,11 @@ void Sys_Error (const char *error, ...) COM_WorkerAbort(string); printf ("Fatal error: %s\n",string); - tcsetattr(STDIN_FILENO, TCSADRAIN, &orig); + if (!noconinput) + { + tcsetattr(STDIN_FILENO, TCSADRAIN, &orig); + fcntl (STDIN_FILENO, F_SETFL, fcntl (STDIN_FILENO, F_GETFL, 0) & ~FNDELAY); + } //we used to fire sigsegv. this resulted in people reporting segfaults and not the error message that appeared above. resulting in wasted debugging. //abort should trigger a SIGABRT and still give us the same stack trace. should be more useful that way. @@ -497,12 +502,14 @@ Sys_Quit */ void Sys_Quit (void) { - tcsetattr(STDIN_FILENO, TCSADRAIN, &orig); + if (!noconinput) + { + tcsetattr(STDIN_FILENO, TCSADRAIN, &orig); + fcntl (STDIN_FILENO, F_SETFL, fcntl (STDIN_FILENO, F_GETFL, 0) & ~FNDELAY); + } exit (0); // appkit isn't running } -static int do_stdin = 1; - #if 1 static char *Sys_LineInputChar(char *line) { @@ -570,7 +577,11 @@ it to the host command processor ================ */ void Sys_Linebuffer_Callback (struct cvar_s *var, char *oldvalue) -{ +{ //reconfigures the tty to send a char at a time (or line at a time) + + if (noconinput) + return; //oh noes! we already hungup! + changes = orig; if (var->value) { @@ -598,32 +609,64 @@ char *Sys_ConsoleInput (void) } #endif - if (!stdin_ready || !do_stdin) + if (!stdin_ready || noconinput==true) return NULL; // the select didn't say it was ready stdin_ready = false; - if (sys_linebuffer.value == 0) +//libraries and muxers and things can all screw with our stdin blocking state. +//if a server sits around waiting for its never-coming stdin then we're screwed. +//and don't assume that it won't block just because select told us it was readable, select lies. +//so force it non-blocking so we don't get any nasty surprises. +#if defined(__linux__) { - text[0] = getc(stdin); - text[1] = 0; - len = 1; - return Sys_LineInputChar(text); - } - else - { - len = read (0, text, sizeof(text)-1); - if (len == 0) + int fl = fcntl (STDIN_FILENO, F_GETFL, 0); + if (!(fl & FNDELAY)) { - // end of file - do_stdin = 0; + fcntl(STDIN_FILENO, F_SETFL, fl | FNDELAY); +// Sys_Printf(CON_WARNING "stdin flags became blocking - gdb bug?\n"); + } + } +#endif + + len = read (STDIN_FILENO, text, sizeof(text)-1); + if (len < 0) + { + int err = errno; + switch(err) + { + case EINTR: //unix sucks + case EAGAIN: //a select fuckup? + break; + case EIO: + noconinput |= 2; + stdin_ready = true; + return NULL; + default: + Con_Printf("error %i reading from stdin\n", err); + noconinput = true; //we don't know what it was, but don't keep triggering it. return NULL; } - if (len < 1) - return NULL; - text[len-1] = 0; // rip off the /n and terminate - - return text; } + if (noconinput&2) + { //posix job stuff sucks - there's no way to detect when we're directly pushed to the foreground after being backgrounded. + Con_Printf("Welcome back!\n"); + noconinput &= ~2; + } + + /*if (len == 0) + { + // end of file? doesn't really make sense. depend upon sighup instead + Con_Printf("EOF reading from stdin\n"); + noconinput = true; + return NULL; + }*/ + if (len < 1) + return NULL; + text[len-1] = 0; // rip off the /n and terminate + + if (sys_linebuffer.value == 0) + return Sys_LineInputChar(text); + return text; } /* @@ -867,6 +910,16 @@ static int Sys_CheckChRoot(void) return ret; } +#ifdef _POSIX_C_SOURCE +static void SigCont(int code) +{ //lets us know when we regained foreground focus. + int fl = fcntl (STDIN_FILENO, F_GETFL, 0); + if (!(fl & FNDELAY)) + fcntl(STDIN_FILENO, F_SETFL, fl | FNDELAY); + noconinput &= ~2; +} +#endif + /* ============= main @@ -898,6 +951,8 @@ int main(int argc, char *argv[]) useansicolours = false; else useansicolours = (isatty(STDOUT_FILENO) || COM_CheckParm("-colour") || COM_CheckParm("-color")); + if (COM_CheckParm("-nostdin")) + noconinput = true; switch(Sys_CheckChRoot()) { @@ -952,6 +1007,12 @@ int main(int argc, char *argv[]) } #endif +#ifdef _POSIX_C_SOURCE + signal(SIGTTIN, SIG_IGN); //have to ignore this if we want to not lock up when running backgrounded. + signal(SIGCONT, SigCont); + signal(SIGCHLD, SIG_IGN); //mapcluster stuff might leak zombie processes if we don't do this. +#endif + #ifdef SUBSERVERS if (COM_CheckParm("-clusterslave")) @@ -970,8 +1031,8 @@ int main(int argc, char *argv[]) // while (1) { - if (do_stdin) - stdin_ready = NET_Sleep(maxsleep, true); + if (noconinput != true) + stdin_ready |= NET_Sleep(maxsleep, true); else { NET_Sleep(maxsleep, false); diff --git a/plugins/xsv/qux.h b/plugins/xsv/qux.h index 18dcd473e..2d5c021fd 100644 --- a/plugins/xsv/qux.h +++ b/plugins/xsv/qux.h @@ -169,7 +169,6 @@ xfont_t *XS_CreateFont(int id, xclient_t *owner, char *fontname); void XS_CreateInitialResources(void); void XS_DestroyResource(xresource_t *res); void XS_DestroyResourcesOfClient(xclient_t *cl); -void XS_CheckResourceSentinals(void); void XW_ExposeWindow(xwindow_t *root, int x, int y, int width, int height);
AddressHostnameGamedirMapnamePlayers
%s%s%s%s%u/%u
%s%s%s%s%s%u/%u