engine: client: introduce cookie protocol extension, when client connects to the server, it provides randomized 64-bit value to be prefixed into netchan messages

* Prevent clientuseragent from leaking data.
* Process qport in netchan, cleaning up code
This commit is contained in:
Alibek Omarov
2026-05-25 23:29:04 +05:00
committed by a1batross
parent d61983a5d2
commit e5c4e49782
7 changed files with 152 additions and 30 deletions

View File

@@ -13,6 +13,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
*/
#include <inttypes.h>
#include "common.h"
#include "const.h"
#include "server.h"
@@ -301,6 +302,7 @@ static void SV_ConnectClient( netadr_t from )
const char *s;
int extensions;
uint netchan_flags = 0;
uint64_t netchan_cookie = 0;
if( Cmd_Argc() < 5 )
{
@@ -345,6 +347,27 @@ static void SV_ConnectClient( netadr_t from )
qport = Q_atoi( Info_ValueForKey( protinfo, "qport" ));
extensions = Q_atoi( Info_ValueForKey( protinfo, "ext" ));
if( FBitSet( extensions, NET_EXT_NETCHAN_COOKIE ))
{
const char *cookie_str = Info_ValueForKey( protinfo, "cookie" );
if( Q_strlen( cookie_str ) != 16 )
{
SV_RejectConnection( from, "client advertised NET_EXT_NETCHAN_COOKIE but did not supply a cookie\n" );
return;
}
byte buf[8];
COM_HexConvert( cookie_str, 16, buf );
for( int i = 0; i < 8; i++ )
netchan_cookie = ( netchan_cookie << 8 ) | buf[i];
}
// these keys aren't useragent
Info_RemoveKey( protinfo, "cookie" );
Info_RemoveKey( protinfo, "ext" );
Info_RemoveKey( protinfo, "qport" );
s = Cmd_Argv( 4 ); // user info
if( Q_strlen( s ) > sizeof( userinfo ) || !Info_IsValid( s ))
@@ -419,7 +442,7 @@ static void SV_ConnectClient( netadr_t from )
newcl->frames = frames;
newcl->userid = g_userid++; // create unique userid
newcl->state = cs_connected; // now expect "spawn" command
newcl->extensions = FBitSet( extensions, NET_EXT_SPLITSIZE );
newcl->extensions = FBitSet( extensions, NET_EXT_SPLITSIZE | NET_EXT_NETCHAN_COOKIE );
Q_strncpy( newcl->useragent, protinfo, sizeof( newcl->useragent ));
// HACKHACK: can hear all players by default to avoid issues
@@ -429,7 +452,11 @@ static void SV_ConnectClient( netadr_t from )
// initailize netchan
if( !Host_IsLocalClient( ))
SetBits( netchan_flags, NETCHAN_USE_LZSS );
if( FBitSet( newcl->extensions, NET_EXT_NETCHAN_COOKIE ))
SetBits( netchan_flags, NETCHAN_USE_COOKIE );
Netchan_Setup( NS_SERVER, &newcl->netchan, from, qport, newcl, SV_GetFragmentSize, netchan_flags );
if( FBitSet( newcl->extensions, NET_EXT_NETCHAN_COOKIE ))
Netchan_SetCookie( &newcl->netchan, netchan_cookie );
MSG_Init( &newcl->datagram, "Datagram", newcl->datagram_buf, sizeof( newcl->datagram_buf )); // datagram buf
Q_strncpy( newcl->hashedcdkey, Info_ValueForKey( protinfo, "uuid" ), 32 );
@@ -439,6 +466,8 @@ static void SV_ConnectClient( netadr_t from )
protinfo[0] = '\0';
Info_SetValueForKeyf( protinfo, "ext", sizeof( protinfo ), "%d", newcl->extensions );
Info_SetValueForKey( protinfo, "cheats", sv_cheats.value ? "1" : "0", sizeof( protinfo ));
if( FBitSet( newcl->extensions, NET_EXT_NETCHAN_COOKIE ))
Info_SetValueForKeyf( protinfo, "cookie", sizeof( protinfo ), "%016"PRIx64, netchan_cookie );
// send the connect packet to the client
Netchan_OutOfBandPrint( NS_SERVER, from, S2C_CONNECTION" %s", protinfo );

View File

@@ -377,7 +377,7 @@ SV_ReadPackets
static void SV_ReadPackets( void )
{
sv_client_t *cl;
int i, qport;
int i;
size_t curSize;
while( NET_GetPacket( NS_SERVER, &net_from, net_message_buffer, &curSize ))
@@ -391,13 +391,6 @@ static void SV_ReadPackets( void )
continue;
}
// read the qport out of the message so we can fix up
// stupid address translating routers
MSG_Clear( &net_message );
MSG_ReadLong( &net_message ); // sequence number
MSG_ReadLong( &net_message ); // sequence number
qport = (int)MSG_ReadShort( &net_message ) & 0xffff;
// check for packets from connected clients
for( i = 0, sv.current_client = svs.clients; i < svs.maxclients; i++, sv.current_client++ )
{
@@ -409,24 +402,22 @@ static void SV_ReadPackets( void )
if( !NET_CompareBaseAdr( net_from, cl->netchan.remote_address ))
continue;
if( cl->netchan.qport != qport )
if( !Netchan_Process( &cl->netchan, &net_message ))
continue;
// authenticated; safe to adopt the (possibly NAT-rewritten) source port
if( cl->netchan.remote_address.port != net_from.port )
cl->netchan.remote_address.port = net_from.port;
if( Netchan_Process( &cl->netchan, &net_message ))
{
if(( svs.maxclients == 1 && !host_limitlocal.value ) || ( cl->state != cs_spawned ))
SetBits( cl->flags, FCL_SEND_NET_MESSAGE ); // reply at end of frame
if(( svs.maxclients == 1 && !host_limitlocal.value ) || ( cl->state != cs_spawned ))
SetBits( cl->flags, FCL_SEND_NET_MESSAGE ); // reply at end of frame
// this is a valid, sequenced packet, so process it
if( cl->frames != NULL && cl->state != cs_zombie )
{
SV_ExecuteClientMessage( cl, &net_message );
svgame.globals->frametime = sv.frametime;
svgame.globals->time = sv.time;
}
// this is a valid, sequenced packet, so process it
if( cl->frames != NULL && cl->state != cs_zombie )
{
SV_ExecuteClientMessage( cl, &net_message );
svgame.globals->frametime = sv.frametime;
svgame.globals->time = sv.time;
}
// fragmentation/reassembly sending takes priority over all game messages, want this in the future?