shinke1987.net
雑多な備忘録等のはず。
他のカテゴリ・タブ
目次
PR

IPアドレスから国を判別するPHPスクリプト

2025-09-06 2025-09-06
カテゴリ: PHP

参考資料

環境

macOS:Sequoia 15.6.1

PHP:8.4.8

目的と感想

指定したIPアドレスがどこの国のものか知りたい。

下記スクリプトに実行権限与えたら、「./ファイル名 IPアドレス」という形で実行できる。

Macで作ったので、シバンに記載しているPHPのパスは適宜変更が必要かもしれない。

IPアドレスのリストが記載されたファイルをwget等で予め取得しておけば、時間が大幅に短縮される。

wget等で取得していない場合は、$url変数のコメントアウトしている部分を利用すれば良いが、実行完了までに約313秒かかった。

wget等で予めIPアドレスのリストが記載されたファイルを取得している場合は、実行完了までに約0.7秒かかった。

メモリの最大使用量は約79MBと結構使っていた。

もし実際に公開して使うなら、IPアドレスのリストをファイルでなくDB(SQLiteとかでも良さそう?)に保存すれば、もっと早く、もっとメモリ使用量を減らせるかもしれない。
その時はlong形式でもIPアドレスを保存すると良い。

5つのIPアドレスリストにて、IPv4の開始IPアドレスは4672個だった。下記コマンドで確認した。

grep -v -E '^[^\|]+\|\*\|ipv4' delegated-* | grep 'ipv4' | grep 'JP' | awk -F\| '{print $4}' | sort | uniq | wc -l

コード

#!/opt/homebrew/bin/php
<?php

require_once "vendor/autoload.php";
// 国名コードと日本語の国名を紐付けるために次のライブラリを利用している。
// composer require sukohi/country-code

//echo 'argv : ' . $argv[1] . PHP_EOL;


// このスクリプトに引数が与えられなかったら終了する。
if (!isset($argv[1])) {
    exit(1);
}

// 与えられた引数がIPv4を表す形式でなければ終了する。
$formatCheck = preg_match('/^\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}$/', $argv[1]);
if ($formatCheck !== 1) {
    exit(1);
}

//$startMicroTimeFalse = microtime(false);
//$startMicroTimeTrue = microtime(true);
//echo '開始 microtime(false) : ' . $startMicroTimeFalse . PHP_EOL;
//echo '開始 microtime(true) : ' . $startMicroTimeTrue . PHP_EOL;
//echo 'メモリ最大使用量 : '.number_format(memory_get_peak_usage(true) / 1024).' KB'.PHP_EOL;

// RIRの割り当て済みIPアドレスのリスト。
$url = [
    // もしネット上のURLから取得したいなら、このURLを利用すると良い。ただし、試したら完了まで約313秒かかった。
    // あらかじめファイルをwget等で取得しておけば、約0.7秒で完了した。
//    'http://ftp.arin.net/pub/stats/arin/delegated-arin-extended-latest',
//    'http://ftp.ripe.net/pub/stats/ripencc/delegated-ripencc-latest',
//    'http://ftp.apnic.net/pub/stats/apnic/delegated-apnic-latest',
//    'http://ftp.lacnic.net/pub/stats/lacnic/delegated-lacnic-latest',
//    'http://ftp.afrinic.net/pub/stats/afrinic/delegated-afrinic-latest',
    'delegated-arin-extended-latest',
    'delegated-ripencc-latest',
    'delegated-apnic-latest',
    'delegated-lacnic-latest',
    'delegated-afrinic-latest',
];

// ipv4List[国名コード] = [開始IPアドレス, IPアドレスの個数] となる。
// 国名コードはISO3166-1 alpha-2で定義されている。
$ipv4List = [];

for ($i = 0; $i < count($url); $i++) {
    // ファイルから情報を読み込みまとめる。
    $file = fopen(
        $url[$i],
        'r'
    ) or die('ファイルを開けませんでした。');

    // 国を表す文字列があり、ipv4の情報を指定するための正規表現。
    $pattern = '/^.+\|[^*]+\|ipv4\|.+$/';

    while ($line = fgets($file, 4096)) {
        if (preg_match($pattern, $line, $matches) === 1) {
            // ipv4の情報の場合。
            $temp = explode('|', $matches[0]);
//        echo '国 : ' . $temp[1] . PHP_EOL;
//        echo '開始IPアドレス : ' . $temp[3] . PHP_EOL;
//        echo 'IPアドレスの個数 : ' . $temp[4]. PHP_EOL;

            $ipv4List[$temp[1]][] = [$temp[3], $temp[4]];
        }
    }

    fclose($file);
//    echo $url[$i] . ' の読み込みが完了しました。' . PHP_EOL;
}


// 指定されたIPアドレスがどの国で利用されていたのか判別する。
$target = $argv[1];
$longTarget = ip2long($target);


// 指定されたIPアドレスの開始IPアドレスと、それに紐付く国を確認する。
// $startIpAddr[long形式のIPアドレス] = [国名コード, IPアドレスの個数]
$startIpAddr = [];
foreach ($ipv4List as $country => $ipv4) {
    for ($index = 0; $index < count($ipv4); $index++) {
        $longIpAddr = ip2long($ipv4[$index][0]);

        if ($longTarget > $longIpAddr) {
            // long変換後、指定されたIPアドレスより小さいものを$startIpAddrへ格納。
            $startIpAddr[$longIpAddr] = [$country, $ipv4[$index][1]];
        }
    }
}

unset($ipv4List);

// 降順にソート。
krsort($startIpAddr, SORT_NUMERIC);

// $targetのIPアドレスが、$startIpAddrに保存された、1番大きな開始IPアドレスに所属するか確認。
// 確認して含まれれば、$resultに日本語の国名が入る。
// 確認して含まれなければ、不明となる。
$result = '不明';
$minIpAddr = array_key_first($startIpAddr);
$maxIpAddr = $minIpAddr + $startIpAddr[array_key_first($startIpAddr)][1];

if (
    $minIpAddr < $longTarget &&
    $maxIpAddr > $longTarget
) {
    // 国名コードと日本語の国名を紐付ける準備。
    $countryCode = new \Sukohi\CountryCode\CountryCode();
    $locale = 'ja';

    $result = $countryCode->countryName(key: $startIpAddr[array_key_first($startIpAddr)][0], locale: $locale);
}

exit($result . PHP_EOL);

//echo $result.PHP_EOL;
//echo '結果 : '.$result.PHP_EOL;

//echo 'メモリ最大使用量 : '.number_format(memory_get_peak_usage(true) / 1024).' KB'.PHP_EOL;

//$endMicroTimeFalse = microtime(false);
//$endMicroTimeTrue = microtime(true);
//echo '終了 microtime(false) : ' . $endMicroTimeFalse . PHP_EOL;
//echo '終了 microtime(true) : ' . $endMicroTimeTrue . PHP_EOL;
//echo '終了 - 開始 : ' . $endMicroTimeTrue - $startMicroTimeTrue . PHP_EOL;
同一カテゴリの記事