'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.