🎭

【PHP】CIDRに特定IPアドレスが含まれているかをチェックする

2021/05/03に公開

ポイント・結論

  • dotted decimal notationからの変換は、ip2long()という神関数が用意されている
  • CIDRにおけるホスト部の桁数をマスクへ変換するには、ビットシフト演算を活用する
  • CIDRに特定IPアドレスが含まれているかのチェックは、マスクとの論理積が一致しているかで判定できる

PHP: ip2long - Manual
PHP: Bitwise Operators - Manual

実装

CIDR.php
<?php

class CIDR
{
    private int $mask;

    private int $binary_network_prefix;

    /**
     * CIDR constructor.
     *
     * @param string $cidr 例: 127.0.0.0/30
     * @throws Exception
     */
    public function __construct(string $cidr)
    {
        list($network_prefix, $bits) = explode('/', $cidr);

        // 例: $bits = 25なら、11111111111111111111111110000000となる
        $mask = (0xffffffff << (32 - $bits)) & 0xffffffff;
        $binary_network_prefix = ip2long($network_prefix);

        if (($binary_network_prefix & $mask) !== $binary_network_prefix) {
            // 127.0.0.1/5 のようにCIDR表記として間違っている
            throw new Exception('invalid CIDR input.');
        }

        $this->mask = $mask;
        $this->binary_network_prefix = $binary_network_prefix;
    }

    /**
     * @param string $ip 例: 127.0.0.12
     * @return bool
     */
    public function contains(string $ip): bool
    {
        // ネットワーク部が一致しているかを判定する
        return (ip2long($ip) & $this->mask) === $this->binary_network_prefix;
    }
}

$cidr = new CIDR('127.0.0.0/26');

// bool(true) CIDR範囲内
var_dump($cidr->contains('127.0.0.3'));

// bool(false) CIDR範囲外
var_dump($cidr->contains('127.0.0.255'));

// bool(true) ネットワークアドレスも含む
var_dump($cidr->contains('127.0.0.0'));

// bool(true) ブロードキャストアドレスも含む
var_dump($cidr->contains('127.0.0.63'));

// bool(false) ブロードキャストアドレス超過
var_dump($cidr->contains('127.0.0.64'));

注意点

IPアドレスの制限などをアプリケーションのレイヤーで行うのは本当はNG。
基本的にはnginxとかで設定しましょう。

Module ngx_http_access_module

Discussion