mirror of
https://github.com/YGGverse/xash3d-fwgs.git
synced 2025-01-17 18:40:02 +00:00
engine: server: new IP filter, rewritten with IPv6 in mind
This commit is contained in:
parent
b0a889d1a1
commit
2b8b3e1993
@ -50,8 +50,12 @@ typedef struct netadr_s
|
||||
struct
|
||||
{
|
||||
uint32_t type;
|
||||
uint8_t ip[4]; // or last 4 IPv6 octets
|
||||
uint8_t ipx[10]; // or first 10 IPv6 octets
|
||||
union
|
||||
{
|
||||
uint8_t ip[4];
|
||||
uint32_t ip4;
|
||||
};
|
||||
uint8_t ipx[10];
|
||||
};
|
||||
struct
|
||||
{
|
||||
|
@ -21,6 +21,7 @@ GNU General Public License for more details.
|
||||
#define S_WARN "^3Warning:^7 "
|
||||
#define S_ERROR "^1Error:^7 "
|
||||
#define S_USAGE "Usage: "
|
||||
#define S_USAGE_INDENT " "
|
||||
|
||||
#define S_OPENGL_NOTE "^2OpenGL Note:^7 "
|
||||
#define S_OPENGL_WARN "^3OpenGL Warning:^7 "
|
||||
|
@ -818,18 +818,15 @@ static void Host_RunTests( int stage )
|
||||
{
|
||||
case 0: // early engine load
|
||||
memset( &tests_stats, 0, sizeof( tests_stats ));
|
||||
Test_RunLibCommon();
|
||||
Test_RunCommon();
|
||||
Test_RunCmd();
|
||||
Test_RunCvar();
|
||||
TEST_LIST_0;
|
||||
#if !XASH_DEDICATED
|
||||
Test_RunCon();
|
||||
TEST_LIST_0_CLIENT;
|
||||
#endif /* XASH_DEDICATED */
|
||||
break;
|
||||
case 1: // after FS load
|
||||
Test_RunImagelib();
|
||||
TEST_LIST_1;
|
||||
#if !XASH_DEDICATED
|
||||
Test_RunVOX();
|
||||
TEST_LIST_1_CLIENT;
|
||||
#endif
|
||||
Msg( "Done! %d passed, %d failed\n", tests_stats.passed, tests_stats.failed );
|
||||
Sys_Quit();
|
||||
|
@ -581,6 +581,118 @@ static int NET_StringToSockaddr( const char *s, struct sockaddr_storage *sadr, q
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
====================
|
||||
NET_StringToFilterAdr
|
||||
|
||||
====================
|
||||
*/
|
||||
qboolean NET_StringToFilterAdr( const char *s, netadr_t *adr, uint *prefixlen )
|
||||
{
|
||||
char copy[128], *temp;
|
||||
qboolean hasCIDR = false;
|
||||
byte ip6[16];
|
||||
uint len;
|
||||
|
||||
if( !COM_CheckStringEmpty( s ))
|
||||
return false;
|
||||
|
||||
memset( adr, 0, sizeof( *adr ));
|
||||
|
||||
// copy the string and remove CIDR prefix
|
||||
Q_strncpy( copy, s, sizeof( copy ));
|
||||
temp = Q_strrchr( copy, '/' );
|
||||
|
||||
if( temp )
|
||||
{
|
||||
*temp = 0;
|
||||
if( Q_isdigit( temp + 1 ))
|
||||
{
|
||||
len = Q_atoi( temp + 1 );
|
||||
hasCIDR = len != 0;
|
||||
}
|
||||
}
|
||||
|
||||
// try to parse as IPv6 first
|
||||
if( ParseIPv6Addr( copy, ip6, NULL, NULL ))
|
||||
{
|
||||
NET_IP6BytesToNetadr( adr, ip6 );
|
||||
adr->type6 = NA_IP6;
|
||||
|
||||
if( !hasCIDR )
|
||||
*prefixlen = 128;
|
||||
else
|
||||
*prefixlen = len;
|
||||
}
|
||||
else
|
||||
{
|
||||
int num = 0;
|
||||
int octet = 0;
|
||||
|
||||
// parse as ipv4 but we don't need to allow all forms here
|
||||
for( temp = copy; *temp; temp++ )
|
||||
{
|
||||
char c = *temp;
|
||||
|
||||
if( c >= '0' && c <= '9' )
|
||||
{
|
||||
num *= 10;
|
||||
num += c - '0';
|
||||
}
|
||||
else if( c == '.' )
|
||||
{
|
||||
if( num > 255 )
|
||||
return false;
|
||||
|
||||
adr->ip[octet++] = num;
|
||||
num = 0;
|
||||
|
||||
if( octet > 3 )
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if( num > 255 )
|
||||
return false;
|
||||
|
||||
adr->ip[octet++] = num;
|
||||
|
||||
if( !hasCIDR )
|
||||
{
|
||||
int i;
|
||||
|
||||
*prefixlen = 32;
|
||||
|
||||
for( i = 3; i >= 0; i-- )
|
||||
{
|
||||
if( !adr->ip[i] )
|
||||
*prefixlen -= 8;
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32_t mask;
|
||||
|
||||
len = bound( 0, len, 32 );
|
||||
*prefixlen = len;
|
||||
|
||||
// drop unneeded bits
|
||||
mask = htonl( adr->ip4 ) & ( 0xFFFFFFFF << ( 32 - len ));
|
||||
adr->ip4 = ntohl( mask );
|
||||
}
|
||||
|
||||
adr->type = NA_IP;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
====================
|
||||
NET_AdrToString
|
||||
@ -635,17 +747,14 @@ Compares without the port
|
||||
*/
|
||||
qboolean NET_CompareBaseAdr( const netadr_t a, const netadr_t b )
|
||||
{
|
||||
if( a.type != b.type )
|
||||
if( a.type6 != b.type6 )
|
||||
return false;
|
||||
|
||||
if( a.type == NA_LOOPBACK )
|
||||
return true;
|
||||
|
||||
if( a.type == NA_IP )
|
||||
{
|
||||
if( !memcmp( a.ip, b.ip, 4 ))
|
||||
return true;
|
||||
}
|
||||
return a.ip4 == b.ip4;
|
||||
|
||||
if( a.type6 == NA_IP6 )
|
||||
{
|
||||
@ -663,9 +772,9 @@ NET_CompareClassBAdr
|
||||
Compare local masks
|
||||
====================
|
||||
*/
|
||||
qboolean NET_CompareClassBAdr( netadr_t a, netadr_t b )
|
||||
qboolean NET_CompareClassBAdr( const netadr_t a, const netadr_t b )
|
||||
{
|
||||
if( a.type != b.type )
|
||||
if( a.type6 != b.type6 )
|
||||
return false;
|
||||
|
||||
if( a.type == NA_LOOPBACK )
|
||||
@ -681,6 +790,60 @@ qboolean NET_CompareClassBAdr( netadr_t a, netadr_t b )
|
||||
// this check is very dumb and only used for LAN restriction
|
||||
// Actual check is in IsReservedAdr
|
||||
|
||||
// for real mask compare use NET_CompareAdrByMask
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
====================
|
||||
NET_CompareAdrByMask
|
||||
|
||||
Checks if adr is a part of subnet
|
||||
====================
|
||||
*/
|
||||
qboolean NET_CompareAdrByMask( const netadr_t a, const netadr_t b, uint prefixlen )
|
||||
{
|
||||
if( a.type6 != b.type6 || a.type == NA_LOOPBACK )
|
||||
return false;
|
||||
|
||||
if( a.type == NA_IP )
|
||||
{
|
||||
uint32_t ipa = htonl( a.ip4 );
|
||||
uint32_t ipb = htonl( b.ip4 );
|
||||
|
||||
if(( ipa & (( 0xFFFFFFFFU ) << ( 32 - prefixlen ))) == ipb )
|
||||
return true;
|
||||
}
|
||||
else if( a.type6 == NA_IP6 )
|
||||
{
|
||||
uint16_t a_[8], b_[8];
|
||||
size_t check = prefixlen / 16;
|
||||
size_t remaining = prefixlen % 16;
|
||||
|
||||
// convert to 16-bit pieces first
|
||||
NET_NetadrToIP6Bytes( (uint8_t*)a_, &a );
|
||||
NET_NetadrToIP6Bytes( (uint8_t*)b_, &b );
|
||||
|
||||
// check complete hextets first, if not equal, then it's different subnets
|
||||
if( check && memcmp( a_, b_, check * sizeof( uint16_t )))
|
||||
return false;
|
||||
|
||||
// check by bits now, similar to v4 check but with 16-bit type
|
||||
if( remaining )
|
||||
{
|
||||
uint16_t hexa, hexb, mask = 0xFFFFU << ( 16 - remaining );
|
||||
|
||||
hexa = htons( a_[check] );
|
||||
hexb = htons( b_[check] );
|
||||
|
||||
if(( hexa & mask ) == ( hexb & mask ))
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -739,7 +902,7 @@ Compare full address
|
||||
*/
|
||||
qboolean NET_CompareAdr( const netadr_t a, const netadr_t b )
|
||||
{
|
||||
if( a.type != b.type )
|
||||
if( a.type6 != b.type6 )
|
||||
return false;
|
||||
|
||||
if( a.type == NA_LOOPBACK )
|
||||
@ -747,7 +910,7 @@ qboolean NET_CompareAdr( const netadr_t a, const netadr_t b )
|
||||
|
||||
if( a.type == NA_IP )
|
||||
{
|
||||
if( !memcmp( a.ip, b.ip, 4 ) && a.port == b.port )
|
||||
if( a.ip4 == b.ip4 && a.port == b.port )
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
@ -1513,7 +1676,7 @@ static int NET_IPSocket( const char *net_iface, int port, int family )
|
||||
if( family == AF_INET6 )
|
||||
pfamily = PF_INET6;
|
||||
else if( family == AF_INET )
|
||||
pfamily == PF_INET;
|
||||
pfamily = PF_INET;
|
||||
|
||||
if( NET_IsSocketError(( net_socket = socket( pfamily, SOCK_DGRAM, IPPROTO_UDP )) ) )
|
||||
{
|
||||
|
@ -56,11 +56,13 @@ qboolean NET_IsLocalAddress( netadr_t adr );
|
||||
const char *NET_AdrToString( const netadr_t a );
|
||||
const char *NET_BaseAdrToString( const netadr_t a );
|
||||
qboolean NET_IsReservedAdr( netadr_t a );
|
||||
qboolean NET_CompareClassBAdr( netadr_t a, netadr_t b );
|
||||
qboolean NET_CompareClassBAdr( const netadr_t a, const netadr_t b );
|
||||
qboolean NET_StringToAdr( const char *string, netadr_t *adr );
|
||||
qboolean NET_StringToFilterAdr( const char *s, netadr_t *adr, uint *prefixlen );
|
||||
int NET_StringToAdrNB( const char *string, netadr_t *adr );
|
||||
qboolean NET_CompareAdr( const netadr_t a, const netadr_t b );
|
||||
qboolean NET_CompareBaseAdr( const netadr_t a, const netadr_t b );
|
||||
qboolean NET_CompareAdrByMask( const netadr_t a, const netadr_t b, uint prefixlen );
|
||||
qboolean NET_GetPacket( netsrc_t sock, netadr_t *from, byte *data, size_t *length );
|
||||
qboolean NET_BufferToBufferCompress( byte *dest, uint *destLen, byte *source, uint sourceLen );
|
||||
qboolean NET_BufferToBufferDecompress( byte *dest, uint *destLen, byte *source, uint sourceLen );
|
||||
|
@ -26,7 +26,7 @@ extern struct tests_stats_s tests_stats;
|
||||
#define TASSERT( exp ) \
|
||||
_TASSERT( !(exp), Msg( S_ERROR "assert failed at %s:%i\n", __FILE__, __LINE__ ) )
|
||||
#define TASSERT_EQi( val1, val2 ) \
|
||||
_TASSERT( ( val1 ) != ( val2 ), Msg( S_ERROR "assert failed at %s:%i, \"%d\" != \"%d\"\n", __FILE__, __LINE__, #val1, #val2 ))
|
||||
_TASSERT( ( val1 ) != ( val2 ), Msg( S_ERROR "assert failed at %s:%i, \"%d\" != \"%d\"\n", __FILE__, __LINE__, val1, val2 ))
|
||||
#define TASSERT_STR( str1, str2 ) \
|
||||
_TASSERT( Q_strcmp(( str1 ), ( str2 )), Msg( S_ERROR "assert failed at %s:%i, \"%s\" != \"%s\"\n", __FILE__, __LINE__, ( str1 ), ( str2 )))
|
||||
|
||||
@ -37,6 +37,23 @@ void Test_RunCmd( void );
|
||||
void Test_RunCvar( void );
|
||||
void Test_RunCon( void );
|
||||
void Test_RunVOX( void );
|
||||
void Test_RunIPFilter( void );
|
||||
|
||||
#define TEST_LIST_0 \
|
||||
Test_RunLibCommon(); \
|
||||
Test_RunCommon(); \
|
||||
Test_RunCmd(); \
|
||||
Test_RunCvar(); \
|
||||
Test_RunIPFilter();
|
||||
|
||||
#define TEST_LIST_0_CLIENT \
|
||||
Test_RunCon();
|
||||
|
||||
#define TEST_LIST_1 \
|
||||
Test_RunImagelib();
|
||||
|
||||
#define TEST_LIST_1_CLIENT \
|
||||
Test_RunVOX();
|
||||
|
||||
#endif
|
||||
|
||||
|
@ -17,21 +17,13 @@ GNU General Public License for more details.
|
||||
#include "server.h"
|
||||
|
||||
|
||||
typedef struct ipfilter_s
|
||||
{
|
||||
float time;
|
||||
float endTime; // -1 for permanent ban
|
||||
struct ipfilter_s *next;
|
||||
uint mask;
|
||||
uint ip;
|
||||
} ipfilter_t;
|
||||
/*
|
||||
=============================================================================
|
||||
|
||||
static ipfilter_t *ipfilter = NULL;
|
||||
|
||||
|
||||
// TODO: Is IP filter really needed?
|
||||
// TODO: Make it IPv6 compatible, for future expansion
|
||||
PLAYER ID FILTER
|
||||
|
||||
=============================================================================
|
||||
*/
|
||||
typedef struct cidfilter_s
|
||||
{
|
||||
float endTime;
|
||||
@ -67,32 +59,6 @@ static void SV_RemoveID( const char *id )
|
||||
}
|
||||
}
|
||||
|
||||
static void SV_RemoveIP( uint ip, uint mask )
|
||||
{
|
||||
ipfilter_t *filter, *prevfilter = NULL;
|
||||
|
||||
for( filter = ipfilter; filter; filter = filter->next )
|
||||
{
|
||||
if( filter->ip != ip || mask != filter->mask )
|
||||
{
|
||||
prevfilter = filter;
|
||||
continue;
|
||||
}
|
||||
|
||||
if( filter == ipfilter )
|
||||
{
|
||||
ipfilter = ipfilter->next;
|
||||
Mem_Free( filter );
|
||||
return;
|
||||
}
|
||||
|
||||
if( prevfilter )
|
||||
prevfilter->next = filter->next;
|
||||
Mem_Free( filter );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
qboolean SV_CheckID( const char *id )
|
||||
{
|
||||
qboolean ret = false;
|
||||
@ -122,34 +88,6 @@ qboolean SV_CheckID( const char *id )
|
||||
return ret;
|
||||
}
|
||||
|
||||
qboolean SV_CheckIP( netadr_t *addr )
|
||||
{
|
||||
uint ip = addr->ip[0] << 24 | addr->ip[1] << 16 | addr->ip[2] << 8 | addr->ip[3];
|
||||
qboolean ret = false;
|
||||
ipfilter_t *filter;
|
||||
|
||||
for( filter = ipfilter; filter; filter = filter->next )
|
||||
{
|
||||
while( filter->endTime && host.realtime > filter->endTime )
|
||||
{
|
||||
uint rip = filter->ip;
|
||||
uint rmask = filter->mask;
|
||||
SV_RemoveIP( rip, rmask );
|
||||
filter = filter->next;
|
||||
if( !filter )
|
||||
return false;
|
||||
}
|
||||
|
||||
if( (ip & filter->mask) == (filter->ip & filter->mask) )
|
||||
{
|
||||
ret = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void SV_BanID_f( void )
|
||||
{
|
||||
float time = Q_atof( Cmd_Argv( 1 ) );
|
||||
@ -289,167 +227,25 @@ static void SV_WriteID_f( void )
|
||||
FS_Close( f );
|
||||
}
|
||||
|
||||
static qboolean StringToIP( const char *str, const char *maskstr, uint *outip, uint *outmask )
|
||||
{
|
||||
byte ip[4] = {0};
|
||||
byte mask[4] = {0};
|
||||
int i = 0;
|
||||
|
||||
if( *str > '9' || *str < '0' )
|
||||
return false;
|
||||
|
||||
do
|
||||
{
|
||||
while( *str <= '9' && *str >= '0' )
|
||||
{
|
||||
ip[i] *=10;
|
||||
ip[i] += *str - '0';
|
||||
str++;
|
||||
}
|
||||
mask[i] = 255;
|
||||
i++;
|
||||
if( *str != '.' ) break;
|
||||
str++;
|
||||
} while( i < 4 );
|
||||
|
||||
i = 0;
|
||||
|
||||
if( !maskstr || *maskstr > '9' || *maskstr < '0' )
|
||||
goto end;
|
||||
|
||||
do
|
||||
{
|
||||
byte mask1 = 0;
|
||||
while( *maskstr <= '9' && *maskstr >= '0' )
|
||||
{
|
||||
mask1 *=10;
|
||||
mask1 += *maskstr - '0';
|
||||
maskstr++;
|
||||
}
|
||||
mask[i] &= mask1;
|
||||
i++;
|
||||
if( *maskstr != '.' ) break;
|
||||
maskstr++;
|
||||
} while( i < 4 );
|
||||
|
||||
end:
|
||||
*outip = ip[0] << 24 | ip[1] << 16 | ip[2] << 8 | ip[3];
|
||||
if( outmask )
|
||||
*outmask = mask[0] << 24 | mask[1] << 16 | mask[2] << 8 | mask[3];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#define IPARGS(ip) (ip >> 24) & 0xFF, (ip >> 16) & 0xFF, (ip >> 8) & 0xFF, ip & 0xFF
|
||||
static void SV_AddIP_f( void )
|
||||
{
|
||||
float time = Q_atof( Cmd_Argv( 1 ) );
|
||||
const char *ipstr = Cmd_Argv( 2 );
|
||||
const char *maskstr = Cmd_Argv( 3 );
|
||||
uint ip, mask;
|
||||
ipfilter_t *filter;
|
||||
|
||||
if( time )
|
||||
time = host.realtime + time * 60.0f;
|
||||
|
||||
if( !StringToIP( ipstr, maskstr, &ip, &mask ) )
|
||||
{
|
||||
Con_Reportf( "Usage: addip <minutes> <ip> [mask]\n0 minutes for permanent ban\n");
|
||||
return;
|
||||
}
|
||||
|
||||
SV_RemoveIP( ip, mask );
|
||||
|
||||
filter = Mem_Malloc( host.mempool, sizeof( ipfilter_t ) );
|
||||
filter->endTime = time;
|
||||
filter->ip = ip;
|
||||
filter->mask = mask;
|
||||
filter->next = ipfilter;
|
||||
|
||||
ipfilter = filter;
|
||||
}
|
||||
|
||||
static void SV_ListIP_f( void )
|
||||
{
|
||||
ipfilter_t *filter;
|
||||
|
||||
Con_Reportf( "ip ban list\n" );
|
||||
Con_Reportf( "-----------\n" );
|
||||
|
||||
for( filter = ipfilter; filter; filter = filter->next )
|
||||
{
|
||||
if( filter->endTime && host.realtime > filter->endTime )
|
||||
continue; // no negative time
|
||||
|
||||
if( filter->endTime )
|
||||
Con_Reportf( "%d.%d.%d.%d %d.%d.%d.%d expries in %f minutes\n", IPARGS( filter->ip ), IPARGS( filter->mask ), ( filter->endTime - host.realtime ) / 60.0f );
|
||||
else
|
||||
Con_Reportf( "%d.%d.%d.%d %d.%d.%d.%d permanent\n", IPARGS( filter->ip ), IPARGS( filter->mask ) );
|
||||
}
|
||||
}
|
||||
|
||||
static void SV_RemoveIP_f( void )
|
||||
{
|
||||
uint ip, mask;
|
||||
|
||||
if( !StringToIP( Cmd_Argv(1), Cmd_Argv(2), &ip, &mask ) )
|
||||
{
|
||||
Con_Reportf( "Usage: removeip <ip> [mask]\n" );
|
||||
return;
|
||||
}
|
||||
|
||||
SV_RemoveIP( ip, mask );
|
||||
}
|
||||
|
||||
static void SV_WriteIP_f( void )
|
||||
{
|
||||
file_t *f = FS_Open( Cvar_VariableString( "listipcfgfile" ), "w", false );
|
||||
ipfilter_t *filter;
|
||||
|
||||
if( !f )
|
||||
{
|
||||
Con_DPrintf( S_ERROR "Could not write %s\n", Cvar_VariableString( "listipcfgfile" ) );
|
||||
return;
|
||||
}
|
||||
|
||||
FS_Printf( f, "//=======================================================================\n" );
|
||||
FS_Printf( f, "//\t\tCopyright Flying With Gauss Team %s ©\n", Q_timestamp( TIME_YEAR_ONLY ));
|
||||
FS_Printf( f, "//\t\t %s - archive of IP blacklist\n", Cvar_VariableString( "listipcfgfile" ) );
|
||||
FS_Printf( f, "//=======================================================================\n" );
|
||||
|
||||
for( filter = ipfilter; filter; filter = filter->next )
|
||||
if( !filter->endTime ) // only permanent
|
||||
FS_Printf( f, "addip 0 %d.%d.%d.%d %d.%d.%d.%d\n", IPARGS(filter->ip), IPARGS(filter->mask) );
|
||||
|
||||
FS_Close( f );
|
||||
}
|
||||
|
||||
void SV_InitFilter( void )
|
||||
static void SV_InitIDFilter( void )
|
||||
{
|
||||
Cmd_AddRestrictedCommand( "banid", SV_BanID_f, "ban player by ID" );
|
||||
Cmd_AddRestrictedCommand( "listid", SV_ListID_f, "list banned players" );
|
||||
Cmd_AddRestrictedCommand( "removeid", SV_RemoveID_f, "remove player from banned list" );
|
||||
Cmd_AddRestrictedCommand( "writeid", SV_WriteID_f, "write banned.cfg" );
|
||||
Cmd_AddRestrictedCommand( "addip", SV_AddIP_f, "add entry to IP filter" );
|
||||
Cmd_AddRestrictedCommand( "listip", SV_ListIP_f, "list current IP filter" );
|
||||
Cmd_AddRestrictedCommand( "removeip", SV_RemoveIP_f, "remove IP filter" );
|
||||
Cmd_AddRestrictedCommand( "writeip", SV_WriteIP_f, "write listip.cfg" );
|
||||
}
|
||||
|
||||
void SV_ShutdownFilter( void )
|
||||
static void SV_ShutdownIDFilter( void )
|
||||
{
|
||||
ipfilter_t *ipList, *ipNext;
|
||||
cidfilter_t *cidList, *cidNext;
|
||||
|
||||
// should be called manually because banned.cfg is not executed by engine
|
||||
//SV_WriteIP_f();
|
||||
//SV_WriteID_f();
|
||||
|
||||
for( ipList = ipfilter; ipList; ipList = ipNext )
|
||||
{
|
||||
ipNext = ipList->next;
|
||||
Mem_Free( ipList );
|
||||
}
|
||||
Cmd_RemoveCommand( "banid" );
|
||||
Cmd_RemoveCommand( "listid" );
|
||||
Cmd_RemoveCommand( "removeid" );
|
||||
Cmd_RemoveCommand( "writeid" );
|
||||
|
||||
for( cidList = cidfilter; cidList; cidList = cidNext )
|
||||
{
|
||||
@ -458,5 +254,463 @@ void SV_ShutdownFilter( void )
|
||||
}
|
||||
|
||||
cidfilter = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
=============================================================================
|
||||
|
||||
CLIENT IP FILTER
|
||||
|
||||
=============================================================================
|
||||
*/
|
||||
|
||||
typedef struct ipfilter_s
|
||||
{
|
||||
float endTime;
|
||||
struct ipfilter_s *next;
|
||||
netadr_t adr;
|
||||
uint prefixlen;
|
||||
} ipfilter_t;
|
||||
|
||||
static ipfilter_t *ipfilter = NULL;
|
||||
|
||||
static void SV_CleanExpiredIPFilters( void )
|
||||
{
|
||||
ipfilter_t *f, **back;
|
||||
|
||||
back = &ipfilter;
|
||||
while( 1 )
|
||||
{
|
||||
f = *back;
|
||||
if( !f ) return;
|
||||
|
||||
if( f->endTime && host.realtime > f->endTime )
|
||||
{
|
||||
*back = f->next;
|
||||
back = &f->next;
|
||||
|
||||
Mem_Free( f );
|
||||
}
|
||||
else back = &f->next;
|
||||
}
|
||||
}
|
||||
|
||||
static int SV_FilterToString( char *dest, size_t size, qboolean config, ipfilter_t *f )
|
||||
{
|
||||
const char *strformat;
|
||||
|
||||
if( config )
|
||||
{
|
||||
return Q_snprintf( dest, size, "addip 0 %s/%d\n", NET_AdrToString( f->adr ), f->prefixlen );
|
||||
}
|
||||
else if( f->endTime )
|
||||
{
|
||||
return Q_snprintf( dest, size, "%s/%d (%f minutes)", NET_AdrToString( f->adr ), f->prefixlen, f->endTime );
|
||||
}
|
||||
|
||||
return Q_snprintf( dest, size, "%s/%d (permanent)", NET_AdrToString( f->adr ), f->prefixlen );
|
||||
}
|
||||
|
||||
static qboolean SV_IPFilterIncludesIPFilter( ipfilter_t *a, ipfilter_t *b )
|
||||
{
|
||||
if( a->adr.type6 != b->adr.type6 )
|
||||
return false;
|
||||
|
||||
// can't include bigger subnet in small
|
||||
if( a->prefixlen < b->prefixlen )
|
||||
return false;
|
||||
|
||||
if( a->prefixlen == b->prefixlen )
|
||||
return NET_CompareAdr( a->adr, b->adr );
|
||||
|
||||
return NET_CompareAdrByMask( a->adr, b->adr, b->prefixlen );
|
||||
}
|
||||
|
||||
static void SV_RemoveIPFilter( ipfilter_t *toremove, qboolean removeAll, qboolean verbose )
|
||||
{
|
||||
ipfilter_t *f, **back;
|
||||
|
||||
back = &ipfilter;
|
||||
while( 1 )
|
||||
{
|
||||
f = *back;
|
||||
if( !f ) return;
|
||||
|
||||
if( SV_IPFilterIncludesIPFilter( toremove, f ))
|
||||
{
|
||||
if( verbose )
|
||||
{
|
||||
string filterStr;
|
||||
|
||||
SV_FilterToString( filterStr, sizeof( filterStr ), false, f );
|
||||
|
||||
Con_Printf( "%s removed.\n", filterStr );
|
||||
}
|
||||
|
||||
*back = f->next;
|
||||
back = &f->next;
|
||||
|
||||
Mem_Free( f );
|
||||
|
||||
if( !removeAll )
|
||||
break;
|
||||
}
|
||||
else back = &f->next;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
qboolean SV_CheckIP( netadr_t *adr )
|
||||
{
|
||||
// TODO: ip rate limit
|
||||
ipfilter_t *entry = ipfilter;
|
||||
|
||||
for( ; entry; entry = entry->next )
|
||||
{
|
||||
switch( entry->adr.type6 )
|
||||
{
|
||||
case NA_IP:
|
||||
case NA_IP6:
|
||||
if( NET_CompareAdrByMask( *adr, entry->adr, entry->prefixlen ))
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void SV_AddIP_PrintUsage( void )
|
||||
{
|
||||
Con_Printf(S_USAGE "addip <minutes> <ipaddress>\n"
|
||||
S_USAGE_INDENT "addip <minutes> <ipaddress/CIDR>\n"
|
||||
"Use 0 minutes for permanent\n"
|
||||
"ipaddress A.B.C.D/24 is equivalent to A.B.C.0 and A.B.C\n"
|
||||
"NOTE: IPv6 addresses only support prefix format!\n");
|
||||
}
|
||||
|
||||
static void SV_RemoveIP_PrintUsage( void )
|
||||
{
|
||||
Con_Printf(S_USAGE "removeip <ipaddress> [removeAll]\n"
|
||||
S_USAGE_INDENT "removeip <ipaddress/CIDR> [removeAll]\n"
|
||||
"Use removeAll to delete all ip filters which ipaddress or ipaddress/CIDR includes\n");
|
||||
}
|
||||
|
||||
static void SV_ListIP_PrintUsage( void )
|
||||
{
|
||||
Con_Printf(S_USAGE "listip [ipaddress]\n"
|
||||
S_USAGE_INDENT "listip [ipaddress/CIDR]\n");
|
||||
}
|
||||
|
||||
static void SV_AddIP_f( void )
|
||||
{
|
||||
const char *szMinutes = Cmd_Argv( 1 );
|
||||
const char *adr = Cmd_Argv( 2 );
|
||||
ipfilter_t filter, *newfilter;
|
||||
float minutes;
|
||||
int i;
|
||||
|
||||
if( Cmd_Argc() != 3 )
|
||||
{
|
||||
// a1ba: kudos to rehlds for an idea of using CIDR prefixes
|
||||
// in these commands :)
|
||||
SV_AddIP_PrintUsage();
|
||||
return;
|
||||
}
|
||||
|
||||
minutes = Q_atof( szMinutes );
|
||||
if( minutes < 0.1f )
|
||||
minutes = 0;
|
||||
|
||||
if( minutes != 0.0f )
|
||||
filter.endTime = host.realtime + minutes * 60;
|
||||
else filter.endTime = 0;
|
||||
|
||||
if( !NET_StringToFilterAdr( adr, &filter.adr, &filter.prefixlen ) )
|
||||
{
|
||||
Con_Printf( "Invalid IP address!\n" );
|
||||
SV_AddIP_PrintUsage();
|
||||
return;
|
||||
}
|
||||
|
||||
newfilter = Mem_Malloc( host.mempool, sizeof( *newfilter ));
|
||||
newfilter->endTime = filter.endTime;
|
||||
newfilter->adr = filter.adr;
|
||||
newfilter->prefixlen = filter.prefixlen;
|
||||
newfilter->next = ipfilter;
|
||||
|
||||
ipfilter = newfilter;
|
||||
|
||||
for( i = 0; i < svs.maxclients; i++ )
|
||||
{
|
||||
netadr_t clientadr = svs.clients[i].netchan.remote_address;
|
||||
|
||||
if( !NET_CompareAdrByMask( clientadr, filter.adr, filter.prefixlen ))
|
||||
continue;
|
||||
|
||||
SV_ClientPrintf( &svs.clients[i], "The server operator has added you to banned list\n" );
|
||||
SV_DropClient( &svs.clients[i], false );
|
||||
}
|
||||
}
|
||||
|
||||
static void SV_ListIP_f( void )
|
||||
{
|
||||
qboolean haveFilter = false;
|
||||
ipfilter_t filter, *f;
|
||||
|
||||
if( Cmd_Argc() > 2 )
|
||||
{
|
||||
SV_ListIP_PrintUsage();
|
||||
return;
|
||||
}
|
||||
|
||||
if( ipfilter == NULL )
|
||||
{
|
||||
Con_Printf( "IP filter list is empty\n" );
|
||||
return;
|
||||
}
|
||||
|
||||
if( Cmd_Argc() == 2 )
|
||||
{
|
||||
haveFilter = NET_StringToFilterAdr( Cmd_Argv( 1 ), &filter.adr, &filter.prefixlen );
|
||||
|
||||
if( !haveFilter )
|
||||
{
|
||||
Con_Printf( "Invalid IP address!\n" );
|
||||
SV_ListIP_PrintUsage();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Con_Printf( "IP filter list:\n" );
|
||||
|
||||
for( f = ipfilter; f; f = f->next )
|
||||
{
|
||||
string filterStr;
|
||||
|
||||
if( haveFilter && !SV_IPFilterIncludesIPFilter( &filter, f ))
|
||||
continue;
|
||||
|
||||
SV_FilterToString( filterStr, sizeof( filterStr ), false, f );
|
||||
Con_Printf( "%s\n", filterStr );
|
||||
}
|
||||
}
|
||||
|
||||
static void SV_RemoveIP_f( void )
|
||||
{
|
||||
const char *adr = Cmd_Argv( 1 );
|
||||
qboolean removeAll;
|
||||
ipfilter_t filter;
|
||||
int i;
|
||||
|
||||
if( Cmd_Argc() != 2 && Cmd_Argc() != 3 )
|
||||
{
|
||||
SV_RemoveIP_PrintUsage();
|
||||
return;
|
||||
}
|
||||
|
||||
removeAll = Cmd_Argc() == 3 && !Q_strcmp( Cmd_Argv( 2 ), "removeAll" );
|
||||
|
||||
if( !NET_StringToFilterAdr( adr, &filter.adr, &filter.prefixlen ) )
|
||||
{
|
||||
Con_Printf( "Invalid IP address!\n" );
|
||||
SV_RemoveIP_PrintUsage();
|
||||
return;
|
||||
}
|
||||
|
||||
SV_RemoveIPFilter( &filter, removeAll, true );
|
||||
}
|
||||
|
||||
static void SV_WriteIP_f( void )
|
||||
{
|
||||
file_t *fd = FS_Open( Cvar_VariableString( "listipcfgfile" ), "w", true );
|
||||
ipfilter_t *f;
|
||||
|
||||
if( !fd )
|
||||
{
|
||||
Con_Printf( "Couldn't open listip.cfg\n" );
|
||||
return;
|
||||
}
|
||||
|
||||
for( f = ipfilter; f; f = f->next )
|
||||
{
|
||||
string filterStr;
|
||||
int size;
|
||||
|
||||
// do not save temporary bans
|
||||
if( f->endTime )
|
||||
continue;
|
||||
|
||||
size = SV_FilterToString( filterStr, sizeof( filterStr ), true, f );
|
||||
FS_Write( fd, filterStr, size );
|
||||
}
|
||||
|
||||
FS_Close( fd );
|
||||
}
|
||||
|
||||
static void SV_InitIPFilter( void )
|
||||
{
|
||||
Cmd_AddRestrictedCommand( "addip", SV_AddIP_f, "add entry to IP filter" );
|
||||
Cmd_AddRestrictedCommand( "listip", SV_ListIP_f, "list current IP filter" );
|
||||
Cmd_AddRestrictedCommand( "removeip", SV_RemoveIP_f, "remove IP filter" );
|
||||
Cmd_AddRestrictedCommand( "writeip", SV_WriteIP_f, "write listip.cfg" );
|
||||
}
|
||||
|
||||
static void SV_ShutdownIPFilter( void )
|
||||
{
|
||||
ipfilter_t *ipList, *ipNext;
|
||||
|
||||
// should be called manually because banned.cfg is not executed by engine
|
||||
//SV_WriteIP_f();
|
||||
|
||||
for( ipList = ipfilter; ipList; ipList = ipNext )
|
||||
{
|
||||
ipNext = ipList->next;
|
||||
Mem_Free( ipList );
|
||||
}
|
||||
|
||||
ipfilter = NULL;
|
||||
}
|
||||
|
||||
void SV_InitFilter( void )
|
||||
{
|
||||
SV_InitIPFilter();
|
||||
SV_InitIDFilter();
|
||||
}
|
||||
|
||||
void SV_ShutdownFilter( void )
|
||||
{
|
||||
SV_ShutdownIPFilter();
|
||||
SV_ShutdownIDFilter();
|
||||
}
|
||||
|
||||
#if XASH_ENGINE_TESTS
|
||||
|
||||
#include "tests.h"
|
||||
|
||||
void Test_StringToFilterAdr( void )
|
||||
{
|
||||
ipfilter_t f1;
|
||||
int i;
|
||||
struct
|
||||
{
|
||||
const char *str;
|
||||
qboolean valid;
|
||||
int prefixlen;
|
||||
int a, b, c, d;
|
||||
} ipv4tests[] =
|
||||
{
|
||||
{ "127.0.0.0/8", true, 8, 127, 0, 0, 0 },
|
||||
{ "192.168", true, 16, 192, 168, 0, 0 },
|
||||
{ "192.168/23", true, 23, 192, 168, 0, 0 },
|
||||
{ "192.168./23", true, 23, 192, 168, 0, 0 },
|
||||
{ "192.168../23", true, 23, 192, 168, 0, 0 },
|
||||
{ "..192...168/23", false },
|
||||
{ "", false },
|
||||
{ "abcd", false }
|
||||
};
|
||||
struct
|
||||
{
|
||||
const char *str;
|
||||
qboolean valid;
|
||||
int prefixlen;
|
||||
uint8_t x[16];
|
||||
} ipv6tests[] =
|
||||
{
|
||||
{ "::1", true, 128, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 } },
|
||||
{ "fd18:b9d4:65cf:83de::/64", true, 64, { 0xfd, 0x18, 0xb9, 0xd4, 0x65, 0xcf, 0x83, 0xde } },
|
||||
{ "kkljnljkhfjnkj", false },
|
||||
{ "fd8a:63d5:e014:0d62:ffff:ffff:ffff:ffff:ffff", false },
|
||||
};
|
||||
|
||||
for( i = 0; i < ARRAYSIZE( ipv4tests ); i++ )
|
||||
{
|
||||
qboolean ret = NET_StringToFilterAdr( ipv4tests[i].str, &f1.adr, &f1.prefixlen );
|
||||
|
||||
TASSERT_EQi( ret, ipv4tests[i].valid );
|
||||
|
||||
if( ret )
|
||||
{
|
||||
TASSERT_EQi( f1.prefixlen, ipv4tests[i].prefixlen );
|
||||
TASSERT_EQi( f1.adr.ip[0], ipv4tests[i].a );
|
||||
TASSERT_EQi( f1.adr.ip[1], ipv4tests[i].b );
|
||||
TASSERT_EQi( f1.adr.ip[2], ipv4tests[i].c );
|
||||
TASSERT_EQi( f1.adr.ip[3], ipv4tests[i].d );
|
||||
}
|
||||
}
|
||||
|
||||
for( i = 0; i < ARRAYSIZE( ipv6tests ); i++ )
|
||||
{
|
||||
qboolean ret = NET_StringToFilterAdr( ipv6tests[i].str, &f1.adr, &f1.prefixlen );
|
||||
uint8_t x[16];
|
||||
|
||||
TASSERT_EQi( ret, ipv6tests[i].valid );
|
||||
|
||||
if( ret )
|
||||
{
|
||||
TASSERT_EQi( f1.prefixlen, ipv6tests[i].prefixlen );
|
||||
|
||||
NET_NetadrToIP6Bytes( (uint8_t*)x, &f1.adr );
|
||||
|
||||
TASSERT( memcmp( x, ipv6tests[i].x, sizeof( x )) == 0 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Test_IPFilterIncludesIPFilter( void )
|
||||
{
|
||||
qboolean ret;
|
||||
const char *adrs[] =
|
||||
{
|
||||
"127.0.0.1/8", // 0
|
||||
"127.0.0.1", // 1
|
||||
"192.168/16", // 2
|
||||
"fe80::/64", // 3
|
||||
"fe80::96ab:9a49:2944:1808", // 4
|
||||
"2a00:1370:8190:f9eb::/62", // 5
|
||||
"2a00:1370:8190:f9eb:3866:6126:330c:b82b" // 6
|
||||
};
|
||||
ipfilter_t f[7];
|
||||
int i;
|
||||
int tests[][3] =
|
||||
{
|
||||
// ipv4
|
||||
{ 0, 0, true },
|
||||
{ 0, 1, false },
|
||||
{ 1, 0, true },
|
||||
{ 0, 2, false },
|
||||
{ 2, 0, false },
|
||||
|
||||
// mixed
|
||||
{ 0, 3, false },
|
||||
{ 1, 4, false },
|
||||
|
||||
// ipv6
|
||||
{ 3, 3, true },
|
||||
{ 3, 4, false },
|
||||
{ 4, 3, true },
|
||||
{ 5, 3, false },
|
||||
{ 3, 5, false },
|
||||
{ 6, 5, true },
|
||||
};
|
||||
|
||||
for( i = 0; i < 7; i++ )
|
||||
{
|
||||
NET_StringToFilterAdr( adrs[i], &f[i].adr, &f[i].prefixlen );
|
||||
}
|
||||
|
||||
for( i = 0; i < ARRAYSIZE( tests ); i++ )
|
||||
{
|
||||
ret = SV_IPFilterIncludesIPFilter( &f[tests[i][0]], &f[tests[i][1]] );
|
||||
|
||||
TASSERT_EQi( ret, tests[i][2] );
|
||||
}
|
||||
}
|
||||
|
||||
void Test_RunIPFilter( void )
|
||||
{
|
||||
Test_StringToFilterAdr();
|
||||
Test_IPFilterIncludesIPFilter();
|
||||
}
|
||||
|
||||
#endif // XASH_ENGINE_TESTS
|
||||
|
Loading…
x
Reference in New Issue
Block a user