[PHP]long2ip関数完全ガイド – IPアドレス変換の仕組みと実用例

PHP

はじめに

PHPのlong2ip関数は、32ビットの整数値をIPアドレス文字列に変換する関数です。ネットワークプログラミングやIPアドレスの操作において重要な役割を果たします。

今回は、long2ip関数の基本的な使い方から応用例まで、詳しく解説していきます。

long2ip関数とは

基本構文

string long2ip(int $ip)

パラメータ:

  • $ip:32ビット符号付き整数のIPアドレス

戻り値:

  • IPアドレスを表す文字列(例:「192.168.1.1」)

基本的な使用例

// 整数値をIPアドレスに変換
$ip_long = 3232235777;
$ip_string = long2ip($ip_long);
echo $ip_string; // 出力:192.168.1.1

// 別の例
$ip_long = 16777343;
$ip_string = long2ip($ip_long);
echo $ip_string; // 出力:1.0.0.127

IPアドレスの内部表現

なぜ整数で表現するのか

IPアドレスは4つのオクテット(8ビット)で構成されており、合計32ビットです。これを整数として扱うことで、以下のメリットがあります:

  • 計算が高速:IPアドレス範囲の判定や演算が効率的
  • データベース保存:文字列より少ないストレージ容量
  • ソート処理:数値として自然な順序でソート可能

IPアドレスと整数の変換方法

// IPアドレスの各オクテットを分解
function explainIpConversion($ip_string) {
    $parts = explode('.', $ip_string);
    
    echo "IPアドレス: {$ip_string}\n";
    echo "オクテット分解:\n";
    
    $total = 0;
    for ($i = 0; $i < 4; $i++) {
        $value = intval($parts[$i]) * pow(256, 3 - $i);
        $total += $value;
        echo "  {$parts[$i]} × 256^" . (3-$i) . " = {$value}\n";
    }
    
    echo "合計: {$total}\n";
    echo "long2ip({$total}) = " . long2ip($total) . "\n";
}

// 実行例
explainIpConversion('192.168.1.1');

出力結果:

IPアドレス: 192.168.1.1
オクテット分解:
  192 × 256^3 = 3221225472
  168 × 256^2 = 11010048
  1 × 256^1 = 256
  1 × 256^0 = 1
合計: 3232235777
long2ip(3232235777) = 192.168.1.1

ip2longとの組み合わせ

相互変換

long2ipip2longの逆の処理を行います:

$ip_string = '192.168.1.1';

// 文字列 → 整数
$ip_long = ip2long($ip_string);
echo "ip2long('{$ip_string}') = {$ip_long}\n";

// 整数 → 文字列
$ip_back = long2ip($ip_long);
echo "long2ip({$ip_long}) = '{$ip_back}'\n";

// 検証
var_dump($ip_string === $ip_back); // bool(true)

32ビット符号の注意点

32ビットシステムでは、大きなIPアドレスで符号の問題が発生する可能性があります:

// 符号なし整数として扱う場合
function safeIp2long($ip) {
    $long = ip2long($ip);
    if ($long === false) {
        return false;
    }
    
    // 32ビットシステムでの符号問題を回避
    if ($long < 0) {
        $long += 4294967296; // 2^32を加算
    }
    
    return $long;
}

function safeLong2ip($long) {
    // 符号なし32ビット整数の範囲をチェック
    if ($long > 4294967295) {
        $long -= 4294967296;
    }
    
    return long2ip($long);
}

// 使用例
$ip = '255.255.255.255';
$long = safeIp2long($ip);
$back = safeLong2ip($long);
echo "{$ip} → {$long} → {$back}\n";

実用的な応用例

1. IPアドレス範囲の判定

class IPRangeChecker {
    private $start_ip;
    private $end_ip;
    
    public function __construct($start, $end) {
        $this->start_ip = ip2long($start);
        $this->end_ip = ip2long($end);
    }
    
    public function isInRange($ip) {
        $ip_long = ip2long($ip);
        return ($ip_long >= $this->start_ip && $ip_long <= $this->end_ip);
    }
    
    public function getIpList($limit = 10) {
        $ips = [];
        $count = min($limit, $this->end_ip - $this->start_ip + 1);
        
        for ($i = 0; $i < $count; $i++) {
            $ips[] = long2ip($this->start_ip + $i);
        }
        
        return $ips;
    }
}

// 使用例
$ranger = new IPRangeChecker('192.168.1.1', '192.168.1.10');

// 範囲チェック
var_dump($ranger->isInRange('192.168.1.5')); // bool(true)
var_dump($ranger->isInRange('192.168.2.1')); // bool(false)

// IP一覧の取得
$ip_list = $ranger->getIpList();
print_r($ip_list);

2. サブネット計算

class SubnetCalculator {
    public static function getNetworkInfo($ip, $cidr) {
        $ip_long = ip2long($ip);
        $mask_long = -1 << (32 - $cidr);
        
        $network_long = $ip_long & $mask_long;
        $broadcast_long = $network_long | (~$mask_long);
        
        return [
            'network' => long2ip($network_long),
            'broadcast' => long2ip($broadcast_long),
            'subnet_mask' => long2ip($mask_long),
            'first_host' => long2ip($network_long + 1),
            'last_host' => long2ip($broadcast_long - 1),
            'total_hosts' => pow(2, 32 - $cidr) - 2
        ];
    }
}

// 使用例
$subnet_info = SubnetCalculator::getNetworkInfo('192.168.1.100', 24);
print_r($subnet_info);

出力結果:

Array
(
    [network] => 192.168.1.0
    [broadcast] => 192.168.1.255
    [subnet_mask] => 255.255.255.0
    [first_host] => 192.168.1.1
    [last_host] => 192.168.1.254
    [total_hosts] => 254
)

3. データベースでのIP管理

class IPLogger {
    private $pdo;
    
    public function __construct($pdo) {
        $this->pdo = $pdo;
    }
    
    public function logAccess($ip, $user_agent) {
        $ip_long = ip2long($ip);
        
        $stmt = $this->pdo->prepare(
            "INSERT INTO access_logs (ip_long, ip_string, user_agent, created_at) 
             VALUES (?, ?, ?, NOW())"
        );
        
        return $stmt->execute([$ip_long, $ip, $user_agent]);
    }
    
    public function getAccessesByRange($start_ip, $end_ip) {
        $start_long = ip2long($start_ip);
        $end_long = ip2long($end_ip);
        
        $stmt = $this->pdo->prepare(
            "SELECT ip_long, user_agent, created_at 
             FROM access_logs 
             WHERE ip_long BETWEEN ? AND ? 
             ORDER BY created_at DESC"
        );
        
        $stmt->execute([$start_long, $end_long]);
        $results = $stmt->fetchAll();
        
        // IPアドレスを文字列に変換
        foreach ($results as &$row) {
            $row['ip_string'] = long2ip($row['ip_long']);
        }
        
        return $results;
    }
}

// テーブル作成例
$create_table = "
CREATE TABLE access_logs (
    id INT AUTO_INCREMENT PRIMARY KEY,
    ip_long INT UNSIGNED,
    ip_string VARCHAR(15),
    user_agent TEXT,
    created_at DATETIME,
    INDEX idx_ip_long (ip_long),
    INDEX idx_created_at (created_at)
)";

4. IPアドレスのソートとフィルタリング

function sortIpAddresses($ip_array) {
    // IPアドレスを整数値でソート
    usort($ip_array, function($a, $b) {
        return ip2long($a) <=> ip2long($b);
    });
    
    return $ip_array;
}

function filterPrivateIPs($ip_array) {
    $private_ranges = [
        ['10.0.0.0', '10.255.255.255'],
        ['172.16.0.0', '172.31.255.255'],
        ['192.168.0.0', '192.168.255.255']
    ];
    
    return array_filter($ip_array, function($ip) use ($private_ranges) {
        $ip_long = ip2long($ip);
        
        foreach ($private_ranges as $range) {
            $start = ip2long($range[0]);
            $end = ip2long($range[1]);
            
            if ($ip_long >= $start && $ip_long <= $end) {
                return true;
            }
        }
        
        return false;
    });
}

// 使用例
$ips = ['192.168.1.10', '8.8.8.8', '172.16.0.1', '192.168.1.1', '1.1.1.1'];

echo "元のリスト:\n";
print_r($ips);

echo "\nソート後:\n";
$sorted = sortIpAddresses($ips);
print_r($sorted);

echo "\nプライベートIPのみ:\n";
$private = filterPrivateIPs($ips);
print_r($private);

エラーハンドリングとバリデーション

入力値の検証

function validateAndConvert($input) {
    // 整数値の場合
    if (is_numeric($input)) {
        $ip_long = intval($input);
        
        // 32ビット符号なし整数の範囲チェック
        if ($ip_long < 0 || $ip_long > 4294967295) {
            throw new InvalidArgumentException("IP値が範囲外です: {$input}");
        }
        
        $result = long2ip($ip_long);
        if ($result === false) {
            throw new InvalidArgumentException("無効なIP値です: {$input}");
        }
        
        return $result;
    }
    
    // 文字列の場合はそのまま返す(別途検証が必要)
    if (filter_var($input, FILTER_VALIDATE_IP)) {
        return $input;
    }
    
    throw new InvalidArgumentException("無効な入力です: {$input}");
}

// 使用例
try {
    echo validateAndConvert(3232235777) . "\n"; // 192.168.1.1
    echo validateAndConvert('8.8.8.8') . "\n";  // 8.8.8.8
    echo validateAndConvert(-1) . "\n";         // 例外
} catch (InvalidArgumentException $e) {
    echo "エラー: " . $e->getMessage() . "\n";
}

パフォーマンス比較

文字列処理 vs 整数処理

function benchmarkIpOperations() {
    $ips = [];
    
    // テストデータ生成
    for ($i = 0; $i < 10000; $i++) {
        $ips[] = long2ip(rand(16777216, 3758096383)); // 1.0.0.0 - 224.0.0.255
    }
    
    // 文字列での範囲チェック
    $start_time = microtime(true);
    $count1 = 0;
    foreach ($ips as $ip) {
        $parts = explode('.', $ip);
        if ($parts[0] >= 192 && $parts[0] <= 223) {
            $count1++;
        }
    }
    $time1 = microtime(true) - $start_time;
    
    // 整数での範囲チェック
    $start_time = microtime(true);
    $count2 = 0;
    $start_range = ip2long('192.0.0.0');
    $end_range = ip2long('223.255.255.255');
    
    foreach ($ips as $ip) {
        $ip_long = ip2long($ip);
        if ($ip_long >= $start_range && $ip_long <= $end_range) {
            $count2++;
        }
    }
    $time2 = microtime(true) - $start_time;
    
    echo "文字列処理: {$time1}秒 (結果: {$count1})\n";
    echo "整数処理: {$time2}秒 (結果: {$count2})\n";
    echo "速度向上: " . round($time1 / $time2, 2) . "倍\n";
}

benchmarkIpOperations();

まとめ

long2ip関数は、PHPでIPアドレスを効率的に扱うための重要な関数です。

主要なポイント:

  • 32ビット整数をIPアドレス文字列に変換
  • ip2longと組み合わせて相互変換が可能
  • IPアドレスの範囲判定や計算が高速
  • データベースでの保存効率が良い
  • ネットワーク計算に適している

注意点:

  • 32ビットシステムでの符号の問題
  • 入力値の妥当性検証が重要
  • IPv4のみ対応(IPv6は別の関数が必要)

適切に活用することで、ネットワーク関連の処理を効率的に実装できます。

タイトルとURLをコピーしました