vpndetector/laravel-vpn-detector
Composer 安装命令:
composer require vpndetector/laravel-vpn-detector
包简介
VPN, proxy and Tor detection middleware for Laravel, powered by the IP2Proxy database.
README 文档
README
Block VPN, proxy, and Tor traffic in your Laravel application — powered by the IP2Proxy PX2 database.
What it does
Laravel VPN Detector looks up every incoming request's IP address against a local copy of the IP2Proxy database and can:
- Block requests from VPN, public proxy, web proxy, or Tor nodes via a drop-in middleware
- Force a logout when an authenticated user switches to a Tor exit node mid-session
- Keep the database fresh automatically with a built-in Artisan command and scheduler integration
All lookups are done in-process against your own database — no external API calls at runtime, no latency overhead.
How it works
Incoming request
│
▼
getClientIp() Resolves the real IP:
CF-Connecting-IP → X-Forwarded-For → request()->ip()
│
▼
checkProxyType() SELECT from ip2proxy_px2_ipv4/ipv6
WHERE INET_ATON(?) BETWEEN ip_from AND ip_to
│
▼
proxy_type? VPN | TOR | PUB | WEB → blocked
null or other → allowed
Requirements
| Dependency | Version |
|---|---|
| PHP | ^8.3 |
| Laravel | ^12.0 |
| MySQL / MariaDB | Any version supporting INET_ATON / INET6_ATON |
| IP2Proxy account | Free tier works (PX2 database) |
Installation
1. Install via Composer
composer require vpndetector/laravel-vpn-detector
2. Publish config and migrations
# Publish the config file to config/vpn-detector.php php artisan vendor:publish --tag=vpn-detector-config # Publish the migrations php artisan vendor:publish --tag=vpn-detector-migrations # Run the migrations (creates ip2proxy_px2_ipv4 and ip2proxy_px2_ipv6 tables) php artisan migrate
3. Set your IP2Proxy token
Add your download token to .env (get it at ip2location.com):
VPN_DETECTOR_IP2PROXY_TOKEN=your-token-here
4. Import the database
php artisan vpn-detector:update-database
This downloads the PX2 CSV files from IP2Proxy (~300 MB each), extracts them, and bulk-imports them into the two database tables. Expect it to take 2–5 minutes on first run.
Architecture
The package is built around three layers:
VpnDetectionServiceInterface
│
├── VpnDetectionService (base: direct DB lookup)
│
└── CachedVpnDetectionService (decorator: wraps base with a cache layer)
│
└── delegates to VpnDetectionService on cache miss
IpLookupResult DTO is returned by checkProxyType() instead of raw stdClass, giving you typed, expressive access to the result:
$result = app(VpnDetectionServiceInterface::class)->checkProxyType('1.2.3.4'); $result->isBlocked(); // true for VPN | TOR | PUB | WEB $result->isVpn(); $result->isTor(); $result->isPublicProxy(); $result->isWebProxy(); $result->isClean(); // true when proxy_type === '-' $result->toArray(); // ['ip', 'proxy_type', 'country_code', 'country_name', 'blocked']
The VpnDetector Facade provides a clean static interface:
use VpnDetector\Facades\VpnDetector; VpnDetector::isUsingProxy(); VpnDetector::checkProxyType('1.2.3.4'); VpnDetector::getClientIp();
Configuration
After publishing, config/vpn-detector.php exposes:
return [ // IP2Proxy download token (required for database updates) 'ip2proxy_token' => env('VPN_DETECTOR_IP2PROXY_TOKEN'), // HTTP status returned by BlockVpnMiddleware (default: 403) 'block_status_code' => env('VPN_DETECTOR_BLOCK_STATUS', 403), // Cache lookup results (strongly recommended in production) 'cache' => [ 'enabled' => env('VPN_DETECTOR_CACHE_ENABLED', true), 'store' => env('VPN_DETECTOR_CACHE_STORE', null), // null = Laravel default 'ttl' => env('VPN_DETECTOR_CACHE_TTL', 3600), // seconds ], // Scheduler settings for automatic database refresh 'schedule' => [ 'frequency' => env('VPN_DETECTOR_SCHEDULE_FREQUENCY', 'daily'), 'daily_at' => env('VPN_DETECTOR_SCHEDULE_AT', '02:00'), 'timezone' => env('VPN_DETECTOR_SCHEDULE_TIMEZONE', null), 'environments' => ['production', 'staging'], 'without_overlapping' => true, 'run_in_background' => true, ], ];
Full list of .env keys: see .env.example.
Usage
Middleware — block VPN/proxy users
Register BlockVpnMiddleware on any route or group:
use VpnDetector\Http\Middleware\BlockVpnMiddleware; // Single route Route::get('/checkout', CheckoutController::class) ->middleware(BlockVpnMiddleware::class); // Route group Route::middleware(BlockVpnMiddleware::class)->group(function () { Route::get('/dashboard', DashboardController::class); Route::get('/account', AccountController::class); });
Blocked requests receive a 403 Access via VPN or proxy is not permitted. response.
Action — force-logout Tor users
Use CheckVpnBlocked in your own middleware or controller when you want finer control (e.g. only log out authenticated Tor users):
use VpnDetector\Action\CheckVpnBlocked; class EnsureNotTor { public function handle(Request $request, Closure $next) { if (app(CheckVpnBlocked::class)->handle()) { return redirect()->route('login')->withErrors('Session terminated for security reasons.'); } return $next($request); } }
The IP is cached in the session so the database is only queried once per unique IP per session.
Service — direct injection
Inject VpnDetectionService (or its interface) anywhere:
use VpnDetector\Contracts\VpnDetectionServiceInterface; class SecurityController extends Controller { public function __construct(private VpnDetectionServiceInterface $vpn) {} public function status(): JsonResponse { $ip = $this->vpn->getClientIp(); $entry = $this->vpn->checkProxyType($ip); return response()->json([ 'ip' => $ip, 'proxy_type' => $entry?->proxy_type, 'country' => $entry?->country_name, 'blocked' => $this->vpn->isUsingProxy(), ]); } }
Scheduler — keep the database fresh
Add SchedulesVpnDetector to your console kernel to automatically refresh the IP2Proxy data:
use VpnDetector\Traits\SchedulesVpnDetector; class Kernel extends ConsoleKernel { use SchedulesVpnDetector; protected function schedule(Schedule $schedule): void { // Uses frequency from config/vpn-detector.php (default: daily at 02:00) $this->scheduleVpnDetector($schedule); // Or use the production preset (daily at 02:00, background, no overlap) $this->scheduleVpnDetectorForProduction($schedule); // Or pass a custom cron expression $this->scheduleVpnDetector($schedule, '0 3 * * 0'); // every Sunday at 03:00 } }
Database tables
The package creates two tables populated from the IP2Proxy PX2 dataset:
| Column | Type | Description |
|---|---|---|
ip_from |
decimal(39,0) | Start of IP range (integer representation) |
ip_to |
decimal(39,0) | End of IP range |
proxy_type |
varchar(3) | VPN, TOR, PUB, WEB, - |
country_code |
char(2) | ISO 3166-1 alpha-2 |
country_name |
varchar(64) | Full country name |
Both ip_from and ip_to form a composite primary key, allowing efficient range lookups via INET_ATON / INET6_ATON.
Note: IP2Proxy recommends using the IPv6 table for both IPv4 and IPv6 queries, but coverage is incomplete in practice. This package queries both tables, routing by address family.
Benchmark
The package ships with a self-contained benchmark command. It generates 6 000 000 synthetic rows (3M IPv4 + 3M IPv6) into a temporary SQLite file, then measures lookup latency — no real IP2Proxy database or API token required.
# From the package directory (no host app needed) php bin/benchmark # Via Composer script composer benchmark # Inside a Laravel app (after installing the package) php artisan vpn-detector:benchmark # Options (same for all three entry points) php bin/benchmark --rows=6000000 --iterations=2000 php bin/benchmark --rows=500000 # quick smoke test php bin/benchmark --no-cache # skip the cache scenario php bin/benchmark --keep-db # keep SQLite file for inspection
Example output (AMD Ryzen 7, SQLite — MySQL with proper indexes will be significantly faster):
Laravel VPN Detector -- Benchmark
==================================
Dataset : 6,000,000 rows (3,000,000 IPv4 + 3,000,000 IPv6)
Iterations : 1,000 per scenario
Cache : enabled (scenario 2)
Generating 3000000 ipv4 rows... 3,000,000 rows in `ip2proxy_px2_ipv4`.
Generating 3000000 ipv6 rows... 3,000,000 rows in `ip2proxy_px2_ipv6`.
Test pool : 6000 IPs (3000 IPv4 + 3000 IPv6)
-- Scenario 1: Direct DB lookup (no cache) --
1000/1000 [============================] 100%
+--------------+---------------+
| Metric | Value |
+--------------+---------------+
| Iterations | 1,000 |
| DB hits | 1,000 |
| DB misses | 0 |
| Total time | 58,526.344 ms |
| Average | 58.526 ms |
| Min | 180.6 us |
| P50 (median) | 57.965 ms |
| P95 | 110.748 ms |
| P99 | 114.839 ms |
| Max | 136.482 ms |
+--------------+---------------+
-- Scenario 2: Warm cache (array store) --
1000/1000 [============================] 100%
+--------------+----------+
| Metric | Value |
+--------------+----------+
| Iterations | 1,000 |
| DB hits | 1,000 |
| DB misses | 0 |
| Total time | 2.854 ms |
| Average | 2.9 us |
| Min | 2.3 us |
| P50 (median) | 2.4 us |
| P95 | 2.7 us |
| P99 | 2.9 us |
| Max | 390.1 us |
+--------------+----------+
Cache speedup: 20,507.5x faster (avg)
Benchmark complete.
These numbers use SQLite as the backend. Production MySQL/MariaDB with a real IP2Proxy dataset and proper indexes will have lower raw lookup latency; the cache speedup ratio (~20 000x) holds regardless of the database engine.
统计信息
- 总下载量: 0
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 3
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-06-23