diff --git a/src/app/Console/Commands/UpdateIP4NetsCommand.php b/src/app/Console/Commands/UpdateIP4NetsCommand.php new file mode 100644 --- /dev/null +++ b/src/app/Console/Commands/UpdateIP4NetsCommand.php @@ -0,0 +1,214 @@ + 'ftp://ftp.afrinic.net/stats/afrinic/delegated-afrinic-latest', + 'apnic' => 'ftp://ftp.apnic.net/pub/apnic/stats/apnic/delegated-apnic-latest', + 'arin' => 'ftp://ftp.arin.net/pub/stats/arin/delegated-arin-extended-latest', + 'lacnic' => 'ftp://ftp.lacnic.net/pub/stats/lacnic/delegated-lacnic-latest', + 'ripencc' => 'ftp://ftp.ripe.net/ripe/stats/delegated-ripencc-latest' + ]; + + $today = Carbon::now()->toDateString(); + + foreach ($rirs as $rir => $url) { + $file = storage_path("{$rir}-{$today}"); + + if (!is_file($file)) { + $fp = fopen(storage_path("{$rir}-{$today}"), 'w'); + + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, $url); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_FILE, $fp); + curl_exec($curl); + curl_close($curl); + fclose($fp); + } + + $serial = $this->serialFromStatsFile($file); + + $numLines = $this->countLines($file); + + $bar = \App\Utils::createProgressBar( + $this->output, + $numLines, + "Importing IPv4 Networks from {$file}" + ); + + $fp = fopen($file, 'r'); + + $nets = []; + + while (!feof($fp)) { + $line = trim(fgets($fp)); + + if ($line == "") { + continue; + } + + if ((int)$line) { + continue; + } + + if ($line[0] == "#") { + continue; + } + + $items = explode('|', $line); + + if (sizeof($items) < 7) { + continue; + } + + if ($items[1] == "*") { + continue; + } + + if ($items[2] != "ipv4") { + continue; + } + + if ($items[5] == "00000000") { + $items[5] = "19700102"; + } + + if ($items[1] == "" || $items[1] == "ZZ") { + continue; + } + + $bar->advance(); + + $mask = 32 - log($items[4], 2); + + $net = \App\IP4Net::where( + [ + 'net_number' => $items[3], + 'net_mask' => $mask, + 'net_broadcast' => long2ip((ip2long($items[3]) + 2 ** (32 - $mask)) - 1) + ] + )->first(); + + if ($net) { + $net->update( + [ + 'country' => $items[1], + 'serial' => $serial + ] + ); + + continue; + } + + $nets[] = [ + 'net_number' => $items[3], + 'net_mask' => $mask, + 'net_broadcast' => long2ip((ip2long($items[3]) + 2 ** (32 - $mask)) - 1), + 'country' => $items[1], + 'serial' => $serial + ]; + + if (sizeof($nets) >= 100) { + \App\IP4Net::insert($nets); + $nets = []; + } + } + + if (sizeof($nets) > 0) { + \App\IP4Net::insert($nets); + } + + $bar->finish(); + + $this->info("DONE"); + } + } + + private function countLines($file) + { + $numLines = 0; + + $fh = fopen($file, 'r'); + + while (!feof($fh)) { + $line = trim(fgets($fh)); + + $items = explode('|', $line); + + if (sizeof($items) < 3) { + continue; + } + + if ($items[2] == "ipv4") { + $numLines++; + } + } + + fclose($fh); + + return $numLines; + } + + private function serialFromStatsFile($file) + { + $serial = null; + + $fh = fopen($file, 'r'); + + while (!feof($fh)) { + $line = trim(fgets($fh)); + + $items = explode('|', $line); + + if (sizeof($items) < 2) { + continue; + } + + if ((int)$items[2]) { + $serial = (int)$items[2]; + break; + } + } + + fclose($fh); + + return $serial; + } +} diff --git a/src/app/IP4Net.php b/src/app/IP4Net.php new file mode 100644 --- /dev/null +++ b/src/app/IP4Net.php @@ -0,0 +1,23 @@ + 'CH', + 'country' => \App\Utils::countryForRequest(), 'currency' => 'CHF', /* 'first_name' => '', diff --git a/src/app/Utils.php b/src/app/Utils.php --- a/src/app/Utils.php +++ b/src/app/Utils.php @@ -11,6 +11,92 @@ */ class Utils { + /** + * Count the number of lines in a file. + * + * Useful for progress bars. + * + * @param string $file The filepath to count the lines of. + * + * @return int + */ + public static function countLines($file) + { + $fh = fopen($file, 'rb'); + $numLines = 0; + + while (!feof($fh)) { + $numLines += substr_count(fread($fh, 8192), "\n"); + } + + fclose($fh); + + return $numLines; + } + + /** + * Return the country ISO code for an IP address. + * + * @return string + */ + public static function countryForIP($ip) + { + $query = " + SELECT country FROM ip4nets + WHERE INET_ATON(net_number) <= INET_ATON(?) + AND INET_ATON(net_broadcast) >= INET_ATON(?) + ORDER BY INET_ATON(net_number) DESC LIMIT 1 + "; + + $nets = \Illuminate\Support\Facades\DB::select($query, [$ip, $ip]); + + $country = $nets[0]->country; + + return $country; + } + + /** + * Return the country ISO code for the current request. + */ + public static function countryForRequest() + { + $request = \request(); + + if (!$request) { + return 'CH'; + } + + $ip = $request->ip(); + + return self::countryForIP($ip); + } + + /** + * Shortcut to creating a progress bar of a particular format with a particular message. + * + * @param \Symfony\Component\Console\Output\OutputInterface $output + * @param int $count + * @param string $message + * + * @return \Symfony\Component\Console\Helper\ProgressBar + */ + public static function createProgressBar($output, $count, $message = null) + { + $bar = $output->createProgressBar($count); + + $bar->setFormat( + '%current:7s%/%max:7s% [%bar%] %percent:3s%% %elapsed:7s%/%estimated:-7s% %message% ' + ); + + if ($message) { + $bar->setMessage($message . " ..."); + } + + $bar->start(); + + return $bar; + } + /** * Return the number of days in the month prior to this one. * diff --git a/src/composer.json b/src/composer.json --- a/src/composer.json +++ b/src/composer.json @@ -18,7 +18,6 @@ "barryvdh/laravel-dompdf": "^0.8.6", "doctrine/dbal": "^2.9", "fideloper/proxy": "^4.0", - "geoip2/geoip2": "^2.9", "iatstuti/laravel-nullable-fields": "*", "kolab/net_ldap3": "dev-master", "laravel/framework": "6.*", @@ -31,7 +30,6 @@ "stripe/stripe-php": "^7.29", "swooletw/laravel-swoole": "^2.6", "torann/currency": "^1.0", - "torann/geoip": "^1.0", "tymon/jwt-auth": "^1.0" }, "require-dev": { diff --git a/src/config/geoip.php b/src/config/geoip.php deleted file mode 100644 --- a/src/config/geoip.php +++ /dev/null @@ -1,165 +0,0 @@ - true, - - /* - |-------------------------------------------------------------------------- - | Include Currency in Results - |-------------------------------------------------------------------------- - | - | When enabled the system will do it's best in deciding the user's currency - | by matching their ISO code to a preset list of currencies. - | - */ - - 'include_currency' => true, - - /* - |-------------------------------------------------------------------------- - | Default Service - |-------------------------------------------------------------------------- - | - | Here you may specify the default storage driver that should be used - | by the framework. - | - | Supported: "maxmind_database", "maxmind_api", "ipapi" - | - */ - - 'service' => 'maxmind_database', - - /* - |-------------------------------------------------------------------------- - | Storage Specific Configuration - |-------------------------------------------------------------------------- - | - | Here you may configure as many storage drivers as you wish. - | - */ - - 'services' => [ - - 'maxmind_database' => [ - 'class' => \Torann\GeoIP\Services\MaxMindDatabase::class, - 'database_path' => storage_path('app/geoip.mmdb'), - 'update_url' => 'https://geolite.maxmind.com/download/geoip/database/GeoLite2-City.mmdb.gz', - 'locales' => ['en'], - ], - - 'maxmind_api' => [ - 'class' => \Torann\GeoIP\Services\MaxMindWebService::class, - 'user_id' => env('MAXMIND_USER_ID'), - 'license_key' => env('MAXMIND_LICENSE_KEY'), - 'locales' => ['en'], - ], - - 'ipapi' => [ - 'class' => \Torann\GeoIP\Services\IPApi::class, - 'secure' => true, - 'key' => env('IPAPI_KEY'), - 'continent_path' => storage_path('app/continents.json'), - 'lang' => 'en', - ], - - 'ipgeolocation' => [ - 'class' => \Torann\GeoIP\Services\IPGeoLocation::class, - 'secure' => true, - 'key' => env('IPGEOLOCATION_KEY'), - 'continent_path' => storage_path('app/continents.json'), - 'lang' => 'en', - ], - - 'ipdata' => [ - 'class' => \Torann\GeoIP\Services\IPData::class, - 'key' => env('IPDATA_API_KEY'), - 'secure' => true, - ], - - 'ipfinder' => [ - 'class' => \Torann\GeoIP\Services\IPFinder::class, - 'key' => env('IPFINDER_API_KEY'), - 'secure' => true, - 'locales' => ['en'], - ], - - ], - - /* - |-------------------------------------------------------------------------- - | Default Cache Driver - |-------------------------------------------------------------------------- - | - | Here you may specify the type of caching that should be used - | by the package. - | - | Options: - | - | all - All location are cached - | some - Cache only the requesting user - | none - Disable cached - | - */ - - 'cache' => 'all', - - /* - |-------------------------------------------------------------------------- - | Cache Tags - |-------------------------------------------------------------------------- - | - | Cache tags are not supported when using the file or database cache - | drivers in Laravel. This is done so that only locations can be cleared. - | - */ - - 'cache_tags' => false, - - /* - |-------------------------------------------------------------------------- - | Cache Expiration - |-------------------------------------------------------------------------- - | - | Define how long cached location are valid. - | - */ - - 'cache_expires' => 30, - - /* - |-------------------------------------------------------------------------- - | Default Location - |-------------------------------------------------------------------------- - | - | Return when a location is not found. - | - */ - - 'default_location' => [ - 'ip' => '127.0.0.0', - 'iso_code' => 'CH', - 'country' => 'Switzerland', - 'city' => 'Zurich', - 'state' => 'ZH', - 'state_name' => 'Zurich', - 'postal_code' => '8703', - 'lat' => 47.30, - 'lon' => 8.59, - 'timezone' => 'Europe/Zurich', - 'continent' => 'EU', - 'default' => true, - 'currency' => 'CHF', - ], - -]; diff --git a/src/database/migrations/2020_06_04_140800_create_ip4nets_table.php b/src/database/migrations/2020_06_04_140800_create_ip4nets_table.php new file mode 100644 --- /dev/null +++ b/src/database/migrations/2020_06_04_140800_create_ip4nets_table.php @@ -0,0 +1,43 @@ +bigIncrements('id'); + $table->string('net_number', 15)->index(); + $table->tinyInteger('net_mask')->unsigned(); + $table->string('net_broadcast', 15)->index(); + $table->string('country', 2)->nullable(); + $table->bigInteger('serial')->unsigned(); + $table->longtext('whois')->nullable(); + $table->timestamps(); + + $table->index(['net_number', 'net_mask', 'net_broadcast']); + } + ); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('ip4nets'); + } +}