'operation_mode', 'type' => 'select', 'label' => 'Operation Mode', 'description' => 'Choose how the extension operates with multiple API providers.', 'required' => true, 'options' => [ 'multi' => 'Multi-Mode (Use all available providers)', 'check' => 'Check Mode (IPv4→IPHunter, IPv6→Others)', 'single' => 'Single Provider Mode (Legacy)', ], 'value' => $values['operation_mode'] ?? 'multi', ], [ 'name' => 'api_provider', 'type' => 'select', 'label' => 'Primary API Provider (Single Mode)', 'description' => 'Choose your primary VPN detection API provider for single mode.', 'required' => false, 'options' => [ 'iphunter' => 'IPHunter.info', 'ipgeolocation' => 'IPGeolocation.io (Security API - Paid)', 'isproxyip' => 'IsProxyIP.com (Free 1K/day, IPv4+IPv6)', 'getipintel' => 'GetIPIntel.net (Free 500/day, IPv4+IPv6)', ], 'value' => $values['api_provider'] ?? 'iphunter', ], [ 'name' => 'iphunter_api_key', 'type' => 'password', 'label' => 'IPHunter.info API Key', 'description' => 'Enter your IPHunter.info API key (IPv4 only).', 'required' => false, 'value' => $values['iphunter_api_key'] ?? '', ], [ 'name' => 'ipgeolocation_api_key', 'type' => 'password', 'label' => 'IPGeolocation.io API Key', 'description' => 'Enter your IPGeolocation.io Security API key (IPv4+IPv6).', 'required' => false, 'value' => $values['ipgeolocation_api_key'] ?? '', ], [ 'name' => 'isproxyip_api_key', 'type' => 'password', 'label' => 'IsProxyIP.com API Key', 'description' => 'Enter your IsProxyIP.com API key (IPv4+IPv6, Free 1K/day).', 'required' => false, 'value' => $values['isproxyip_api_key'] ?? '', ], [ 'name' => 'getipintel_api_key', 'type' => 'password', 'label' => 'GetIPIntel.net Contact Email', 'description' => 'Enter your valid email address for GetIPIntel.net (Free 500/day, IPv4+IPv6). Must be a real email that can receive messages.', 'required' => false, 'value' => $values['getipintel_api_key'] ?? '', ], [ 'name' => 'provider_api_key', 'type' => 'password', 'label' => 'Legacy API Key (Single Mode)', 'description' => 'Legacy field for single mode operation. Use individual API keys above for multi-mode.', 'required' => false, 'value' => $values['provider_api_key'] ?? '', ], [ 'name' => 'block_on_detect', 'type' => 'checkbox', 'label' => 'Block VPN/Proxy', 'description' => 'Block when VPN/Proxy is detected by any provider.', 'required' => false, 'value' => $values['block_on_detect'] ?? true, ], [ 'name' => 'block_on_suspicious', 'type' => 'checkbox', 'label' => 'Block Suspicious IPs', 'description' => 'Block when IP is flagged as suspicious by any provider.', 'required' => false, 'value' => $values['block_on_suspicious'] ?? true, ], [ 'name' => 'multi_mode_consensus', 'type' => 'select', 'label' => 'Multi-Mode Consensus', 'description' => 'How to handle results when multiple providers are used.', 'required' => false, 'options' => [ 'any' => 'Block if ANY provider detects threat', 'majority' => 'Block if MAJORITY of providers detect threat', 'all' => 'Block only if ALL providers detect threat', ], 'value' => $values['multi_mode_consensus'] ?? 'any', ], [ 'name' => 'ipv6_handling', 'type' => 'select', 'label' => 'IPv6 Handling (Check Mode)', 'description' => 'How to handle IPv6 addresses in check mode. Note: IPHunter only supports IPv4.', 'required' => false, 'options' => [ 'convert' => 'Try to convert to IPv4 first (recommended for IPHunter)', 'allow' => 'Allow all IPv6', 'block' => 'Block all IPv6', 'native' => 'Use native IPv6 support (IPGeolocation/IsProxyIP/GetIPIntel only)', ], 'value' => $values['ipv6_handling'] ?? 'convert', ], [ 'name' => 'whitelist', 'type' => 'textarea', 'label' => 'Whitelisted IPs', 'description' => 'IPs to never block (one per line). Supports both IPv4 and IPv6.', 'required' => false, 'value' => $values['whitelist'] ?? '', ], [ 'name' => 'test_mode', 'type' => 'checkbox', 'label' => 'Test Mode (Force Block)', 'description' => 'Block ALL IPs for testing (DANGER - can lock you out).', 'required' => false, 'value' => $values['test_mode'] ?? false, ], [ 'name' => 'enable_caching', 'type' => 'checkbox', 'label' => 'Enable IP Caching', 'description' => 'Cache API results to reduce API calls and improve performance.', 'required' => false, 'value' => $values['enable_caching'] ?? true, ], [ 'name' => 'cache_ttl', 'type' => 'number', 'label' => 'Cache TTL (minutes)', 'description' => 'How long to cache IP results in minutes (1-1440).', 'required' => false, 'value' => $values['cache_ttl'] ?? 60, ], [ 'name' => 'graceful_fallback', 'type' => 'checkbox', 'label' => 'Graceful Fallback', 'description' => 'Allow requests through when API returns unexpected responses.', 'required' => false, 'value' => $values['graceful_fallback'] ?? true, ], [ 'name' => 'debug_mode', 'type' => 'checkbox', 'label' => 'Debug Mode', 'description' => 'Enable detailed logging for troubleshooting. Disable in production for better performance.', 'required' => false, 'value' => $values['debug_mode'] ?? false, ], ]; } public function boot(): void { if (app()->runningInConsole()) { return; } // Only log boot once to avoid spam static $booted = false; if (!$booted) { Log::info('AntiVPN Extension: Starting boot'); $booted = true; } $router = app('router'); $router->pushMiddlewareToGroup('web', \Paymenter\Extensions\Others\AntiVPN\AntiVPNMiddleware::class); // Only log registration once to avoid spam static $registered = false; if (!$registered) { Log::info('AntiVPN Extension: Middleware registered'); $registered = true; } } public static function getInstance(): self { return app(self::class); } } class AntiVPNMiddleware { private $debugMode = false; public function handle(Request $request, Closure $next) { $this->debugLog('=== AntiVPN Middleware START ==='); $this->debugLog('Request URL: ' . $request->fullUrl()); $this->debugLog('Request Method: ' . $request->method()); $this->debugLog('Request Path: ' . $request->path()); // Skip admin routes if (str_starts_with($request->path(), 'admin')) { $this->debugLog('Skipping admin route'); return $next($request); } // Only check GET requests if (!$request->isMethod('GET')) { $this->debugLog('Skipping non-GET request'); return $next($request); } try { $extension = AntiVPN::getInstance(); $this->debugLog('Extension instance loaded'); // Get config $operationMode = $extension->config('operation_mode', 'multi') ?? 'multi'; $apiProvider = $extension->config('api_provider', 'iphunter') ?? 'iphunter'; $iphunterKey = $extension->config('iphunter_api_key', '') ?? ''; $ipgeolocationKey = $extension->config('ipgeolocation_api_key', '') ?? ''; $isproxyipKey = $extension->config('isproxyip_api_key', '') ?? ''; $getipintelKey = $extension->config('getipintel_api_key', '') ?? ''; $legacyKey = $extension->config('provider_api_key', '') ?? ''; $blockOnDetect = $extension->config('block_on_detect', true) ?? true; $blockOnSuspicious = $extension->config('block_on_suspicious', true) ?? true; $multiModeConsensus = $extension->config('multi_mode_consensus', 'any') ?? 'any'; $ipv6Handling = $extension->config('ipv6_handling', 'allow') ?? 'allow'; $testMode = $extension->config('test_mode', false) ?? false; $enableCaching = $extension->config('enable_caching', true) ?? true; $cacheTtl = max(1, min(1440, $extension->config('cache_ttl', 60) ?? 60)); // Clamp between 1-1440 $gracefulFallback = $extension->config('graceful_fallback', true) ?? true; $debugMode = $extension->config('debug_mode', false) ?? false; $this->debugMode = $debugMode; $this->debugLog('Config loaded - Operation Mode: ' . $operationMode); $this->debugLog('Config loaded - IPHunter Key: ' . (!empty($iphunterKey) ? 'SET' : 'EMPTY')); $this->debugLog('Config loaded - IPGeolocation Key: ' . (!empty($ipgeolocationKey) ? 'SET' : 'EMPTY')); $this->debugLog('Config loaded - IsProxyIP Key: ' . (!empty($isproxyipKey) ? 'SET' : 'EMPTY')); $this->debugLog('Config loaded - GetIPIntel Key: ' . (!empty($getipintelKey) ? 'SET' : 'EMPTY')); $this->debugLog('Config loaded - Legacy Key: ' . (!empty($legacyKey) ? 'SET' : 'EMPTY')); // Debug: Show actual values (truncated for security) $this->debugLog('IsProxyIP Key (first 10 chars): ' . substr($isproxyipKey, 0, 10)); $this->debugLog('GetIPIntel Key (first 10 chars): ' . substr($getipintelKey, 0, 10)); // Check if we have any valid API keys $hasValidKeys = !empty($iphunterKey) || !empty($ipgeolocationKey) || !empty($isproxyipKey) || !empty($getipintelKey) || !empty($legacyKey); if (!$hasValidKeys && !$testMode) { $this->debugLog('No API keys and not test mode - allowing'); return $next($request); } $ip = $this->getRealIp($request); $this->debugLog('Real IP detected: ' . $ip); // Test mode - block everyone (except admin) if ($testMode) { Log::warning('AntiVPN: TEST MODE - BLOCKING IP: ' . $ip); return $this->getBlockResponse($request, $ip, 'TEST'); } // Check whitelist if ($this->isWhitelisted($ip, $extension)) { $this->debugLog('IP is whitelisted - allowing'); return $next($request); } // Store original IP for logging $originalIp = $ip; // Determine which providers to use based on operation mode $providers = []; if ($operationMode === 'multi') { // Multi-mode: Use all available providers if (!empty($iphunterKey)) { $providers['iphunter'] = $iphunterKey; } if (!empty($ipgeolocationKey)) { $providers['ipgeolocation'] = $ipgeolocationKey; } if (!empty($isproxyipKey)) { $providers['isproxyip'] = $isproxyipKey; } if (!empty($getipintelKey) && filter_var($getipintelKey, FILTER_VALIDATE_EMAIL)) { $this->debugLog('Multi mode - Using GetIPIntel key (valid email)'); $providers['getipintel'] = $getipintelKey; } elseif (!empty($getipintelKey)) { Log::warning('AntiVPN: GetIPIntel key appears to be encrypted/invalid, skipping GetIPIntel provider'); $this->debugLog('Multi mode - GetIPIntel key value: ' . substr($getipintelKey, 0, 20) . '...'); } } elseif ($operationMode === 'check') { // Check mode: IPv4 → IPHunter, IPv6 → Others if ($this->isIPv6($ip)) { $this->debugLog('Check mode - IPv6 detected, using IPv6-capable providers'); if (!empty($ipgeolocationKey)) { $providers['ipgeolocation'] = $ipgeolocationKey; } if (!empty($isproxyipKey)) { $providers['isproxyip'] = $isproxyipKey; } if (!empty($getipintelKey) && filter_var($getipintelKey, FILTER_VALIDATE_EMAIL)) { $this->debugLog('Check mode - Using GetIPIntel key (valid email)'); $providers['getipintel'] = $getipintelKey; } elseif (!empty($getipintelKey)) { Log::warning('AntiVPN: GetIPIntel key appears to be encrypted/invalid, skipping GetIPIntel provider'); $this->debugLog('Check mode - GetIPIntel key value: ' . substr($getipintelKey, 0, 20) . '...'); } // Handle IPv6 conversion if needed if (empty($providers) && $ipv6Handling === 'convert') { $convertedIp = $this->convertIPv6ToIPv4($ip); if ($convertedIp !== null && !empty($iphunterKey)) { $this->debugLog('Check mode - IPv6 converted to IPv4, using IPHunter'); $providers['iphunter'] = $iphunterKey; // Update IP for API call $ip = $convertedIp; } } } else { $this->debugLog('Check mode - IPv4 detected, using IPHunter'); if (!empty($iphunterKey)) { $providers['iphunter'] = $iphunterKey; } elseif (!empty($ipgeolocationKey)) { $providers['ipgeolocation'] = $ipgeolocationKey; } elseif (!empty($isproxyipKey)) { $providers['isproxyip'] = $isproxyipKey; } elseif (!empty($getipintelKey) && filter_var($getipintelKey, FILTER_VALIDATE_EMAIL)) { $providers['getipintel'] = $getipintelKey; } elseif (!empty($getipintelKey)) { Log::warning('AntiVPN: GetIPIntel key appears to be encrypted/invalid, skipping GetIPIntel provider'); } } } else { // Single mode: Prioritize individual keys over legacy key $this->debugLog('Single mode - Selected provider: ' . $apiProvider); // Check if we have individual keys for the selected provider first $useIndividualKey = false; if ($apiProvider === 'iphunter' && !empty($iphunterKey)) { $this->debugLog('Single mode - Using IPHunter individual key'); $providers['iphunter'] = $iphunterKey; $useIndividualKey = true; } elseif ($apiProvider === 'ipgeolocation' && !empty($ipgeolocationKey)) { $this->debugLog('Single mode - Using IPGeolocation individual key'); $providers['ipgeolocation'] = $ipgeolocationKey; $useIndividualKey = true; } elseif ($apiProvider === 'isproxyip' && !empty($isproxyipKey)) { $this->debugLog('Single mode - Using IsProxyIP individual key'); $providers['isproxyip'] = $isproxyipKey; $useIndividualKey = true; } elseif ($apiProvider === 'getipintel' && !empty($getipintelKey) && filter_var($getipintelKey, FILTER_VALIDATE_EMAIL)) { $this->debugLog('Single mode - Using GetIPIntel individual key (valid email)'); $this->debugLog('Single mode - GetIPIntel key value: ' . substr($getipintelKey, 0, 20) . '...'); $providers['getipintel'] = $getipintelKey; $useIndividualKey = true; } elseif ($apiProvider === 'getipintel' && !empty($getipintelKey)) { Log::warning('AntiVPN: GetIPIntel selected but email is invalid, falling back to other providers'); // Try other providers as fallback if (!empty($iphunterKey)) { $this->debugLog('Single mode - Fallback to IPHunter'); $providers['iphunter'] = $iphunterKey; $useIndividualKey = true; } elseif (!empty($isproxyipKey)) { $this->debugLog('Single mode - Fallback to IsProxyIP'); $providers['isproxyip'] = $isproxyipKey; $useIndividualKey = true; } elseif (!empty($ipgeolocationKey)) { $this->debugLog('Single mode - Fallback to IPGeolocation'); $providers['ipgeolocation'] = $ipgeolocationKey; $useIndividualKey = true; } } // Only use legacy key if no individual key was found if (!$useIndividualKey && !empty($legacyKey)) { $this->debugLog('Single mode - No individual key found, using legacy key for provider: ' . $apiProvider); $this->debugLog('Single mode - Legacy key value: ' . substr($legacyKey, 0, 20) . '...'); $providers[$apiProvider] = $legacyKey; } elseif (!$useIndividualKey) { Log::warning('AntiVPN: Single mode - No valid key found for selected provider: ' . $apiProvider); } } if (empty($providers)) { $this->debugLog('No valid providers available - allowing'); return $next($request); } $this->debugLog('Using providers: ' . implode(', ', array_keys($providers))); $this->debugLog('Selected provider in single mode: ' . $apiProvider); // Skip private IPs (IPv4 only, or if we converted from IPv6) if (!$this->isIPv6($ip) && !filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) { $this->debugLog('Private IPv4 - allowing'); return $next($request); } // Check cache first $cacheKey = 'antivpn_' . md5($ip . '_' . implode('_', array_keys($providers))); $results = null; if ($enableCaching) { $cachedResult = Cache::get($cacheKey); if ($cachedResult !== null) { $this->debugLog('Using cached result for IP: ' . $ip); $results = $cachedResult; } } // Check APIs if not cached if ($results === null) { $results = $this->checkMultipleApis($ip, $providers, $gracefulFallback); $this->debugLog('API results: ' . json_encode($results)); // Cache the results if ($enableCaching && !empty($results)) { $cacheMinutes = max(1, min(1440, $cacheTtl)); // Clamp between 1-1440 minutes Cache::put($cacheKey, $results, now()->addMinutes($cacheMinutes)); $this->debugLog('Cached result for IP: ' . $ip . ' (TTL: ' . $cacheMinutes . ' minutes)'); } } // Decision logic based on operation mode $shouldBlock = false; $blockReason = null; $blockProvider = null; if ($operationMode === 'multi') { $decision = $this->evaluateMultiModeResults($results, $blockOnDetect, $blockOnSuspicious, $multiModeConsensus); $shouldBlock = $decision['should_block']; $blockReason = $decision['reason']; $blockProvider = $decision['provider'] ?? null; } else { // Single mode or check mode - use first available result $result = !empty($results) ? reset($results) : null; if ($result !== null) { if ($result['is_vpn'] && $blockOnDetect) { Log::warning('AntiVPN: BLOCKING - VPN detected and block_on_detect enabled'); $shouldBlock = true; $blockReason = 'VPN'; $blockProvider = $result['provider'] ?? null; } elseif ($result['is_suspicious'] && $blockOnSuspicious) { Log::warning('AntiVPN: BLOCKING - Suspicious IP detected and block_on_suspicious enabled'); $shouldBlock = true; $blockReason = 'Suspicious'; $blockProvider = $result['provider'] ?? null; } else { $this->debugLog('NOT BLOCKING - VPN: ' . ($result['is_vpn']?'Y':'N') . ', Suspicious: ' . ($result['is_suspicious']?'Y':'N') . ', Block VPN: ' . ($blockOnDetect?'Y':'N') . ', Block Suspicious: ' . ($blockOnSuspicious?'Y':'N')); } } } if ($shouldBlock) { return $this->getBlockResponse($request, $originalIp, $blockReason, $blockProvider); } } catch (\Exception $e) { Log::error('AntiVPN: Exception - ' . $e->getMessage()); Log::error('AntiVPN: Stack trace - ' . $e->getTraceAsString()); } $this->debugLog('Allowing request'); $this->debugLog('=== AntiVPN Middleware END ==='); return $next($request); } private function checkMultipleApis(string $ip, array $providers, bool $gracefulFallback = true): array { $results = []; foreach ($providers as $provider => $apiKey) { try { $this->debugLog("Checking {$provider} for IP: {$ip}"); $result = $this->checkApi($ip, $apiKey, $provider, $gracefulFallback); if ($result !== null) { $results[$provider] = $result; $this->debugLog("{$provider} result - VPN: " . ($result['is_vpn'] ? 'Y' : 'N') . ", Suspicious: " . ($result['is_suspicious'] ? 'Y' : 'N')); } else { Log::warning("AntiVPN: {$provider} returned null result"); if ($gracefulFallback) { Log::info("AntiVPN: Graceful fallback enabled - allowing request due to {$provider} failure"); return []; // Return empty results to allow request } } } catch (\Exception $e) { Log::error("AntiVPN: {$provider} API exception - " . $e->getMessage()); if ($gracefulFallback) { Log::info("AntiVPN: Graceful fallback enabled - allowing request due to {$provider} exception"); return []; // Return empty results to allow request } } } return $results; } private function evaluateMultiModeResults(array $results, bool $blockOnDetect, bool $blockOnSuspicious, string $consensus): array { if (empty($results)) { return ['should_block' => false, 'reason' => null, 'provider' => null]; } // Filter out empty/null results and fallback results $validResults = array_filter($results, function($result) { return $result !== null && !isset($result['fallback']); }); if (empty($validResults)) { Log::info('AntiVPN: Multi-mode - No valid results available, allowing request'); return ['should_block' => false, 'reason' => 'no_valid_results', 'provider' => null]; } $vpnDetections = 0; $suspiciousDetections = 0; $totalProviders = count($validResults); $detectingProviders = []; foreach ($validResults as $provider => $result) { if ($result['is_vpn']) { $vpnDetections++; $detectingProviders[] = $provider; } if ($result['is_suspicious']) { $suspiciousDetections++; $detectingProviders[] = $provider; } } $this->debugLog("Multi-mode evaluation - Valid providers: {$totalProviders}, VPN detections: {$vpnDetections}, Suspicious detections: {$suspiciousDetections}, Consensus: {$consensus}"); // If we have fewer valid results than expected, log it $originalCount = count($results); if ($totalProviders < $originalCount) { $skippedCount = $originalCount - $totalProviders; $this->debugLog("Multi-mode - Skipped {$skippedCount} invalid/fallback results"); } $shouldBlock = false; $reason = null; $provider = null; // Ensure consensus is valid, default to 'any' if not if (!in_array($consensus, ['any', 'majority', 'all'])) { Log::warning("AntiVPN: Invalid consensus value '{$consensus}', defaulting to 'any'"); $consensus = 'any'; } if ($consensus === 'any') { if ($vpnDetections > 0 && $blockOnDetect) { $shouldBlock = true; $reason = 'VPN (any)'; $provider = implode(', ', array_unique($detectingProviders)); } elseif ($suspiciousDetections > 0 && $blockOnSuspicious) { $shouldBlock = true; $reason = 'Suspicious (any)'; $provider = implode(', ', array_unique($detectingProviders)); } } elseif ($consensus === 'majority') { $majorityThreshold = ceil($totalProviders / 2); if ($vpnDetections >= $majorityThreshold && $blockOnDetect) { $shouldBlock = true; $reason = 'VPN (majority)'; $provider = implode(', ', array_unique($detectingProviders)); } elseif ($suspiciousDetections >= $majorityThreshold && $blockOnSuspicious) { $shouldBlock = true; $reason = 'Suspicious (majority)'; $provider = implode(', ', array_unique($detectingProviders)); } } elseif ($consensus === 'all') { if ($vpnDetections === $totalProviders && $blockOnDetect) { $shouldBlock = true; $reason = 'VPN (all)'; $provider = implode(', ', array_unique($detectingProviders)); } elseif ($suspiciousDetections === $totalProviders && $blockOnSuspicious) { $shouldBlock = true; $reason = 'Suspicious (all)'; $provider = implode(', ', array_unique($detectingProviders)); } } if (!$shouldBlock) { $this->debugLog("Multi-mode - No threats detected by any provider"); } return ['should_block' => $shouldBlock, 'reason' => $reason, 'provider' => $provider]; } private function getRealIp(Request $request): string { // Try Cloudflare first if (!empty($_SERVER['HTTP_CF_CONNECTING_IP'])) { return trim($_SERVER['HTTP_CF_CONNECTING_IP']); } // Try X-Forwarded-For if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { $ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']); return trim($ips[0]); } return $request->ip(); } private function isIPv6(string $ip): bool { return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false; } private function convertIPv6ToIPv4(string $ipv6): ?string { try { // Remove brackets if present $ipv6 = trim($ipv6, '[]'); // 1. Check for IPv4-mapped IPv6 (::ffff:192.0.2.1) if (preg_match('/^::ffff:([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})$/i', $ipv6, $matches)) { $this->debugLog('Found IPv4-mapped IPv6'); return $matches[1]; } // 2. Check for IPv4-compatible IPv6 (::192.0.2.1) if (preg_match('/^::([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})$/', $ipv6, $matches)) { $this->debugLog('Found IPv4-compatible IPv6'); return $matches[1]; } // 3. Check for embedded IPv4 in last 32 bits (2001:db8::192.0.2.1) if (preg_match('/^([0-9a-f:]+)::([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})$/i', $ipv6, $matches)) { $this->debugLog('Found embedded IPv4 in IPv6'); return $matches[2]; } // 4. Try to extract IPv4 from Teredo tunneling (2001:0000::/32) if (preg_match('/^2001:0000:([0-9a-f]{4}):([0-9a-f]{4}):([0-9a-f]{4}):([0-9a-f]{4}):([0-9a-f]{4}):([0-9a-f]{4})$/i', $ipv6, $matches)) { $this->debugLog('Attempting Teredo IPv4 extraction'); // In Teredo, the client IPv4 is in the last 32 bits (XORed with 0xFFFFFFFF) $clientIPv4Hex = $matches[5] . $matches[6]; $clientIPv4Int = hexdec($clientIPv4Hex) ^ 0xFFFFFFFF; $ipv4 = long2ip($clientIPv4Int); if (filter_var($ipv4, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { return $ipv4; } } // 5. Check for 6to4 tunneling (2002::/16) if (preg_match('/^2002:([0-9a-f]{4}):([0-9a-f]{4}):/i', $ipv6, $matches)) { $this->debugLog('Attempting 6to4 IPv4 extraction'); // In 6to4, the IPv4 address is encoded in the first 32 bits after 2002: $ipv4Hex = $matches[1] . $matches[2]; $ipv4Int = hexdec($ipv4Hex); $ipv4 = long2ip($ipv4Int); if (filter_var($ipv4, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { return $ipv4; } } $this->debugLog('No IPv4 conversion method applicable for: ' . $ipv6); return null; } catch (\Exception $e) { Log::error('AntiVPN: IPv6 to IPv4 conversion failed: ' . $e->getMessage()); return null; } } private function isWhitelisted(string $ip, $extension): bool { $whitelist = trim($extension->config('whitelist', '')); if (empty($whitelist)) { return false; } $whitelistIps = array_filter(array_map('trim', explode("\n", $whitelist))); return in_array($ip, $whitelistIps, true); } private function checkApi(string $ip, string $apiKey, string $provider, bool $gracefulFallback = true): ?array { try { $this->debugLog("Making API call to {$provider} for IP: {$ip}"); if ($provider === 'iphunter') { return $this->checkIPHunterApi($ip, $apiKey, $gracefulFallback); } elseif ($provider === 'ipgeolocation') { return $this->checkIPGeolocationApi($ip, $apiKey, $gracefulFallback); } elseif ($provider === 'isproxyip') { return $this->checkIsProxyIPApi($ip, $apiKey, $gracefulFallback); } elseif ($provider === 'getipintel') { return $this->checkGetIPIntelApi($ip, $apiKey, $gracefulFallback); } Log::error('AntiVPN: Unknown API provider: ' . $provider); return null; } catch (\Exception $e) { Log::error('AntiVPN: API exception - ' . $e->getMessage()); return null; } } private function checkIPHunterApi(string $ip, string $apiKey, bool $gracefulFallback = true): ?array { try { $response = Http::timeout(10) ->withHeaders([ 'X-Key' => $apiKey, 'Accept' => 'application/json', 'User-Agent' => 'AntiVPN-Extension/2.1.0', ]) ->get("https://www.iphunter.info:8082/v1/ip/{$ip}"); $this->debugLog('IPHunter response status: ' . $response->status()); $this->debugLog('IPHunter response body: ' . $response->body()); if (!$response->successful()) { Log::error('AntiVPN: IPHunter API request failed with status: ' . $response->status()); if ($gracefulFallback) { Log::info('AntiVPN: Graceful fallback - allowing request due to IPHunter API failure'); return ['is_vpn' => false, 'is_suspicious' => false, 'provider' => 'iphunter', 'fallback' => true]; } return null; } $data = $response->json(); $this->debugLog('IPHunter parsed data: ' . json_encode($data)); if (isset($data['status']) && $data['status'] === 'success' && isset($data['data']['block'])) { $blockLevel = (int) $data['data']['block']; return [ 'is_vpn' => $blockLevel === 1, 'is_suspicious' => $blockLevel === 2, 'provider' => 'iphunter', 'raw_response' => $data ]; } Log::warning('AntiVPN: Unexpected IPHunter API response structure'); if ($gracefulFallback) { Log::info('AntiVPN: Graceful fallback - allowing request due to unexpected IPHunter response'); return ['is_vpn' => false, 'is_suspicious' => false, 'provider' => 'iphunter', 'fallback' => true]; } return null; } catch (\Exception $e) { Log::error('AntiVPN: IPHunter API exception - ' . $e->getMessage()); return null; } } private function checkIPGeolocationApi(string $ip, string $apiKey, bool $gracefulFallback = true): ?array { try { $response = Http::timeout(10) ->withHeaders([ 'Accept' => 'application/json', 'User-Agent' => 'AntiVPN-Extension/2.1.0', ]) ->get("https://api.ipgeolocation.io/v2/security", [ 'apiKey' => $apiKey, 'ip' => $ip ]); $this->debugLog('IPGeolocation response status: ' . $response->status()); $this->debugLog('IPGeolocation response body: ' . $response->body()); if (!$response->successful()) { Log::error('AntiVPN: IPGeolocation API request failed with status: ' . $response->status()); if ($gracefulFallback) { Log::info('AntiVPN: Graceful fallback - allowing request due to IPGeolocation API failure'); return ['is_vpn' => false, 'is_suspicious' => false, 'provider' => 'ipgeolocation', 'fallback' => true]; } return null; } $data = $response->json(); $this->debugLog('IPGeolocation parsed data: ' . json_encode($data)); // Check for error in response if (isset($data['message'])) { Log::error('AntiVPN: IPGeolocation API error: ' . $data['message']); if ($gracefulFallback) { Log::info('AntiVPN: Graceful fallback - allowing request due to IPGeolocation API error'); return ['is_vpn' => false, 'is_suspicious' => false, 'provider' => 'ipgeolocation', 'fallback' => true]; } return null; } // IPGeolocation.io Security API structure $isProxy = false; $isThreat = false; if (isset($data['security'])) { $security = $data['security']; // Check for proxy/VPN if (isset($security['is_proxy']) && $security['is_proxy']) { $isProxy = true; } // Check for Tor if (isset($security['is_tor']) && $security['is_tor']) { $isProxy = true; // Treat Tor as proxy/VPN } // Check for various threat indicators if (isset($security['is_anonymous']) && $security['is_anonymous']) { $isThreat = true; } if (isset($security['is_known_attacker']) && $security['is_known_attacker']) { $isThreat = true; } if (isset($security['is_spam']) && $security['is_spam']) { $isThreat = true; } if (isset($security['is_bot']) && $security['is_bot']) { $isThreat = true; } // High threat score if (isset($security['threat_score']) && $security['threat_score'] > 50) { $isThreat = true; } $this->debugLog('IPGeolocation security analysis - Proxy: ' . ($isProxy ? 'Y' : 'N') . ', Threat: ' . ($isThreat ? 'Y' : 'N')); $this->debugLog('IPGeolocation threat details - Threat Score: ' . ($security['threat_score'] ?? 'N/A') . ', Proxy Type: ' . ($security['proxy_type'] ?? 'N/A') . ', Provider: ' . ($security['proxy_provider'] ?? 'N/A')); } return [ 'is_vpn' => $isProxy, 'is_suspicious' => $isThreat, 'provider' => 'ipgeolocation', 'raw_response' => $data ]; } catch (\Exception $e) { Log::error('AntiVPN: IPGeolocation API exception - ' . $e->getMessage()); return null; } } private function checkIsProxyIPApi(string $ip, string $apiKey, bool $gracefulFallback = true): ?array { try { $response = Http::timeout(10) ->withHeaders([ 'Accept' => 'application/json', 'User-Agent' => 'AntiVPN-Extension/2.1.0', ]) ->get("https://api.isproxyip.com/v1/check.php", [ 'key' => $apiKey, 'ip' => $ip, 'format' => 'json' ]); $this->debugLog('IsProxyIP response status: ' . $response->status()); $this->debugLog('IsProxyIP response body: ' . $response->body()); if (!$response->successful()) { Log::error('AntiVPN: IsProxyIP API request failed with status: ' . $response->status()); if ($gracefulFallback) { Log::info('AntiVPN: Graceful fallback - allowing request due to IsProxyIP API failure'); return ['is_vpn' => false, 'is_suspicious' => false, 'provider' => 'isproxyip', 'fallback' => true]; } return null; } $data = $response->json(); $this->debugLog('IsProxyIP parsed data: ' . json_encode($data)); // Check for error in response if (isset($data['status']) && $data['status'] === 'error') { Log::error('AntiVPN: IsProxyIP API error: ' . ($data['message'] ?? 'Unknown error')); if ($gracefulFallback) { Log::info('AntiVPN: Graceful fallback - allowing request due to IsProxyIP API error'); return ['is_vpn' => false, 'is_suspicious' => false, 'provider' => 'isproxyip', 'fallback' => true]; } return null; } // IsProxyIP API structure - simple proxy detection $isProxy = false; if (isset($data['status']) && $data['status'] === 'success' && isset($data['proxy'])) { $isProxy = (bool) $data['proxy']; // 1 = proxy, 0 = not proxy $this->debugLog('IsProxyIP proxy detection: ' . ($isProxy ? 'PROXY' : 'CLEAN')); } return [ 'is_vpn' => $isProxy, 'is_suspicious' => false, // IsProxyIP only detects proxy/VPN, not suspicious behavior 'provider' => 'isproxyip', 'raw_response' => $data ]; } catch (\Exception $e) { Log::error('AntiVPN: IsProxyIP API exception - ' . $e->getMessage()); return null; } } private function getBlockResponse(Request $request, string $ip, $blockLevel, $provider = null) { if ($request->expectsJson()) { return response()->json([ 'error' => 'Access blocked by AntiVPN', 'ip' => $ip, 'level' => $blockLevel, 'provider' => $provider, ], 403); } $dateandtime = date('Y-m-d H:i:s'); $providerText = $provider ? "Detected by: {$provider}{$ip}" : ''; $html = " Access Restricted | Virtnet.bond

Access Restricted

VPN or Proxy Connection Detected

Access from virtual private networks (VPNs) or proxies is strictly prohibited. You must access this page using a standard, non-VPN/proxy internet connection. To maintain network integrity and prevent abuse, Virtnet.bond restricts access from these services.

If you believe this is an error, you may request a review by contacting abuse@virtnet.bond or submitting a support ticket.

Logged IP: {$ip}

Date/Time:

Your connection details are logged in accordance with our security and data protection policies. For more information, please review our Privacy Policy.

Contact Support Previous Page
© 2025 Virtnet.bond. All rights reserved.
"; return response($html, 403)->header('Content-Type', 'text/html'); } private function checkGetIPIntelApi(string $ip, string $contactEmail, bool $gracefulFallback = true): ?array { try { $this->debugLog('GetIPIntel API called with contact email: ' . substr($contactEmail, 0, 20) . '...'); // Validate email format if (!filter_var($contactEmail, FILTER_VALIDATE_EMAIL)) { Log::error('AntiVPN: GetIPIntel contact email is not valid: ' . $contactEmail); Log::error('AntiVPN: GetIPIntel contact email appears to be encrypted. Please check your configuration.'); return null; } $response = Http::timeout(10) ->withHeaders([ 'Accept' => 'application/json', 'User-Agent' => 'AntiVPN-Extension/2.1.0', ]) ->get("http://check.getipintel.net/check.php", [ 'ip' => $ip, 'contact' => $contactEmail, 'format' => 'json', 'flags' => 'm' // Use dynamic ban list only for fastest response ]); $this->debugLog('GetIPIntel response status: ' . $response->status()); $this->debugLog('GetIPIntel response body: ' . $response->body()); if (!$response->successful()) { Log::error('AntiVPN: GetIPIntel API request failed with status: ' . $response->status()); if ($gracefulFallback) { Log::info('AntiVPN: Graceful fallback - allowing request due to GetIPIntel API failure'); return ['is_vpn' => false, 'is_suspicious' => false, 'provider' => 'getipintel', 'fallback' => true]; } return null; } $data = $response->json(); $this->debugLog('GetIPIntel parsed data: ' . json_encode($data)); // Check for error in response if (isset($data['status']) && $data['status'] === 'error') { $errorCode = $data['result'] ?? 'unknown'; $errorMessage = $data['message'] ?? 'Unknown error'; // Handle specific error codes switch ($errorCode) { case '-6': Log::error('AntiVPN: GetIPIntel API error -6: Invalid contact email. Please provide a valid email address.'); break; case '-5': Log::error('AntiVPN: GetIPIntel API error -5: IP banned or rate limit exceeded.'); break; case '-4': Log::error('AntiVPN: GetIPIntel API error -4: Database unavailable.'); break; case '-3': Log::error('AntiVPN: GetIPIntel API error -3: Private/unroutable IP address.'); break; case '-2': Log::error('AntiVPN: GetIPIntel API error -2: Invalid IP address format.'); break; case '-1': Log::error('AntiVPN: GetIPIntel API error -1: No input provided.'); break; default: Log::error('AntiVPN: GetIPIntel API error ' . $errorCode . ': ' . $errorMessage); } if ($gracefulFallback) { Log::info('AntiVPN: Graceful fallback - allowing request due to GetIPIntel API error'); return ['is_vpn' => false, 'is_suspicious' => false, 'provider' => 'getipintel', 'fallback' => true]; } return null; } // GetIPIntel returns a score between 0-1 $score = isset($data['result']) ? (float) $data['result'] : 0.0; // According to GetIPIntel docs: // Values > 0.95 should be looked at // Values > 0.99 are most likely proxies // Values below 0.90 are considered "low risk" $isProxy = $score >= 0.95; $isSuspicious = $score >= 0.90 && $score < 0.95; $this->debugLog('GetIPIntel score: ' . $score . ' - Proxy: ' . ($isProxy ? 'Y' : 'N') . ', Suspicious: ' . ($isSuspicious ? 'Y' : 'N')); return [ 'is_vpn' => $isProxy, 'is_suspicious' => $isSuspicious, 'provider' => 'getipintel', 'raw_response' => $data, 'score' => $score ]; } catch (\Exception $e) { Log::error('AntiVPN: GetIPIntel API exception - ' . $e->getMessage()); return null; } } /** * Debug logging helper - only logs when debug mode is enabled */ private function debugLog(string $message): void { if ($this->debugMode) { Log::info('AntiVPN DEBUG: ' . $message); } } }