@@ -152,6 +152,9 @@ public static void runTicker()
152152 // Basic Ban Checks (Ignore Whitelist)
153153 CheckAndPuntBannedPlayers ( ) ;
154154
155+ // Proxy/VPN/TOR and Geo-Blocking Checks
156+ CheckAndPuntProxyViolations ( ) ;
157+
155158 }
156159 }
157160 else
@@ -744,5 +747,194 @@ private static string GetListStatus(string ipAddress, DateTime now)
744747
745748 return comments . Count > 0 ? string . Join ( " | " , comments ) : "OK" ;
746749 }
750+
751+ // Check active players against proxy/VPN/TOR rules and geo-blocking
752+ public static void CheckAndPuntProxyViolations ( )
753+ {
754+ // Only run if proxy checking is enabled
755+ if ( ! theInstance . proxyCheckEnabled )
756+ return ;
757+
758+ DateTime now = DateTime . Now ;
759+
760+ // Cycle through all players in the player list
761+ foreach ( var kvp in theInstance . playerList )
762+ {
763+ int slotNum = kvp . Key ;
764+ playerObject player = kvp . Value ;
765+
766+ // Only check players who were seen in the last 4 seconds (active players)
767+ if ( ( now - player . PlayerLastSeen ) . TotalSeconds > 4 )
768+ continue ;
769+
770+ // Skip if player IP is not available
771+ if ( string . IsNullOrEmpty ( player . PlayerIPAddress ) )
772+ continue ;
773+
774+ if ( ! IPAddress . TryParse ( player . PlayerIPAddress , out IPAddress ? playerIP ) )
775+ continue ;
776+
777+ // Check if player is whitelisted (skip proxy checks for whitelisted players)
778+ bool isWhitelisted = false ;
779+
780+ // Check name whitelist
781+ foreach ( var whitelistedName in banInstance . WhitelistedNames )
782+ {
783+ if ( whitelistedName . RecordType == banInstanceRecordType . Information )
784+ continue ;
785+
786+ if ( whitelistedName . RecordType == banInstanceRecordType . Temporary &&
787+ whitelistedName . ExpireDate . HasValue &&
788+ now > whitelistedName . ExpireDate . Value )
789+ continue ;
790+
791+ if ( player . PlayerName . Equals ( whitelistedName . PlayerName , StringComparison . OrdinalIgnoreCase ) )
792+ {
793+ isWhitelisted = true ;
794+ break ;
795+ }
796+ }
797+
798+ // Check IP whitelist
799+ if ( ! isWhitelisted )
800+ {
801+ foreach ( var whitelistedIP in banInstance . WhitelistedIPs )
802+ {
803+ if ( whitelistedIP . RecordType == banInstanceRecordType . Information )
804+ continue ;
805+
806+ if ( whitelistedIP . RecordType == banInstanceRecordType . Temporary &&
807+ whitelistedIP . ExpireDate . HasValue &&
808+ now > whitelistedIP . ExpireDate . Value )
809+ continue ;
810+
811+ if ( IsIPMatch ( playerIP , whitelistedIP . PlayerIP , whitelistedIP . SubnetMask ) )
812+ {
813+ isWhitelisted = true ;
814+ break ;
815+ }
816+ }
817+ }
818+
819+ // Skip whitelisted players
820+ if ( isWhitelisted )
821+ continue ;
822+
823+ // Find proxy record for this player's IP
824+ var proxyRecord = banInstance . ProxyRecords
825+ . FirstOrDefault ( p => p . IPAddress . Equals ( playerIP ) ) ;
826+
827+ // If no proxy record exists or cache expired, trigger a check and skip for now
828+ if ( proxyRecord == null || now > proxyRecord . CacheExpiry )
829+ {
830+ CheckIP ( playerIP ) ;
831+ continue ;
832+ }
833+
834+ bool shouldPunt = false ;
835+ bool shouldBan = false ;
836+ string puntReason = string . Empty ;
837+
838+ // Check Proxy
839+ if ( proxyRecord . IsProxy && theInstance . proxyCheckProxyAction > 0 )
840+ {
841+ shouldPunt = true ;
842+ shouldBan = theInstance . proxyCheckProxyAction == 2 ;
843+ puntReason = $ "Proxy detected{ ( shouldBan ? " (Auto-banned)" : " (Kicked)" ) } ";
844+ AppDebug . Log ( "tickerBanManagement" , $ "Player '{ player . PlayerName } ' (Slot { slotNum } , IP: { player . PlayerIPAddress } ) is using a PROXY. Action: { ( shouldBan ? "Ban" : "Kick" ) } ") ;
845+ }
846+
847+ // Check VPN
848+ if ( ! shouldPunt && proxyRecord . IsVpn && theInstance . proxyCheckVPNAction > 0 )
849+ {
850+ shouldPunt = true ;
851+ shouldBan = theInstance . proxyCheckVPNAction == 2 ;
852+ puntReason = $ "VPN detected{ ( shouldBan ? " (Auto-banned)" : " (Kicked)" ) } ";
853+ if ( ! string . IsNullOrEmpty ( proxyRecord . Provider ) )
854+ puntReason += $ " - { proxyRecord . Provider } ";
855+ AppDebug . Log ( "tickerBanManagement" , $ "Player '{ player . PlayerName } ' (Slot { slotNum } , IP: { player . PlayerIPAddress } ) is using a VPN. Action: { ( shouldBan ? "Ban" : "Kick" ) } ") ;
856+ }
857+
858+ // Check TOR
859+ if ( ! shouldPunt && proxyRecord . IsTor && theInstance . proxyCheckTORAction > 0 )
860+ {
861+ shouldPunt = true ;
862+ shouldBan = theInstance . proxyCheckTORAction == 2 ;
863+ puntReason = $ "TOR detected{ ( shouldBan ? " (Auto-banned)" : " (Kicked)" ) } ";
864+ AppDebug . Log ( "tickerBanManagement" , $ "Player '{ player . PlayerName } ' (Slot { slotNum } , IP: { player . PlayerIPAddress } ) is using TOR. Action: { ( shouldBan ? "Ban" : "Kick" ) } ") ;
865+ }
866+
867+ // Check Geo-Blocking
868+ if ( ! shouldPunt && theInstance . proxyCheckGeoMode > 0 && ! string . IsNullOrEmpty ( proxyRecord . CountryCode ) )
869+ {
870+ bool countryInList = banInstance . ProxyBlockedCountries
871+ . Any ( c => c . CountryCode . Equals ( proxyRecord . CountryCode , StringComparison . OrdinalIgnoreCase ) ) ;
872+
873+ // Mode 1 = Block listed countries
874+ // Mode 2 = Allow only listed countries
875+ bool shouldBlockCountry = theInstance . proxyCheckGeoMode == 1 ? countryInList : ! countryInList ;
876+
877+ if ( shouldBlockCountry )
878+ {
879+ shouldPunt = true ;
880+ shouldBan = false ; // Geo-blocking only kicks, doesn't auto-ban
881+ var country = banInstance . ProxyBlockedCountries
882+ . FirstOrDefault ( c => c . CountryCode . Equals ( proxyRecord . CountryCode , StringComparison . OrdinalIgnoreCase ) ) ;
883+ string countryName = country ? . CountryName ?? proxyRecord . CountryCode ;
884+ puntReason = $ "Geo-blocked: { countryName } ({ proxyRecord . CountryCode } )";
885+ AppDebug . Log ( "tickerBanManagement" , $ "Player '{ player . PlayerName } ' (Slot { slotNum } , IP: { player . PlayerIPAddress } ) from blocked country: { countryName } . Action: Kick") ;
886+ }
887+ }
888+
889+ // Execute action
890+ if ( shouldPunt )
891+ {
892+ if ( shouldBan )
893+ {
894+ // Create ban record
895+ var banRecord = new banInstancePlayerIP
896+ {
897+ RecordID = 0 , // Will be set by database
898+ MatchID = theInstance . gameMatchID ,
899+ PlayerIP = playerIP ,
900+ SubnetMask = 32 , // Exact IP match
901+ Date = now ,
902+ ExpireDate = null , // Permanent ban
903+ AssociatedName = null ,
904+ RecordType = banInstanceRecordType . Permanent ,
905+ RecordCategory = 0 , // Ban
906+ Notes = puntReason
907+ } ;
908+
909+ try
910+ {
911+ // Add to database
912+ int recordId = DatabaseManager . AddPlayerIPRecord ( banRecord ) ;
913+ banRecord . RecordID = recordId ;
914+
915+ // Add to in-memory ban list
916+ banInstance . BannedPlayerIPs . Add ( banRecord ) ;
917+
918+ AppDebug . Log ( "tickerBanManagement" , $ "Auto-banned IP { player . PlayerIPAddress } : { puntReason } ") ;
919+
920+ // Add to NetLimiter filter if enabled
921+ if ( ! string . IsNullOrEmpty ( theInstance . netLimiterFilterName ) )
922+ {
923+ _ = NetLimiterClient . AddIpToFilterAsync ( theInstance . netLimiterFilterName , player . PlayerIPAddress , 32 ) ;
924+ }
925+ }
926+ catch ( Exception ex )
927+ {
928+ AppDebug . Log ( "tickerBanManagement" , $ "Error adding auto-ban record for IP { player . PlayerIPAddress } : { ex . Message } ") ;
929+ }
930+ }
931+
932+ // Punt the player
933+ ServerMemory . WriteMemorySendConsoleCommand ( "punt " + slotNum ) ;
934+ AppDebug . Log ( "tickerBanManagement" , $ "Punting player '{ player . PlayerName } ' (Slot { slotNum } ). Reason: { puntReason } ") ;
935+ }
936+ }
937+ }
938+
747939 }
748940}
0 commit comments