Соответствие IPv6-адреса подсети CIDR

есть ли хороший способ сопоставить IPv6-адрес с подсетью IPv6 с помощью нотации CIDR? То, что я ищу, эквивалентно IPv6 этому: сопоставление IP-адреса с маской CIDR в PHP 5?

приведенный выше пример нельзя использовать, так как IPv6-адрес имеет длину 128 бит, предотвращая работу побитового сдвига влево. Ты можешь придумать другой способ?

EDIT: добавлено мое собственное решение в список ответов.

6 ответов


поскольку вы не можете преобразовать адреса IPv6 в целое число, вы должны работать с битами, например:

$ip='21DA:00D3:0000:2F3B:02AC:00FF:FE28:9C5A';
$cidrnet='21DA:00D3:0000:2F3B::/64';

// converts inet_pton output to string with bits
function inet_to_bits($inet) 
{
   $unpacked = unpack('A16', $inet);
   $unpacked = str_split($unpacked[1]);
   $binaryip = '';
   foreach ($unpacked as $char) {
             $binaryip .= str_pad(decbin(ord($char)), 8, '0', STR_PAD_LEFT);
   }
   return $binaryip;
}    

$ip = inet_pton($ip);
$binaryip=inet_to_bits($ip);

list($net,$maskbits)=explode('/',$cidrnet);
$net=inet_pton($net);
$binarynet=inet_to_bits($net);

$ip_net_bits=substr($binaryip,0,$maskbits);
$net_bits   =substr($binarynet,0,$maskbits);

if($ip_net_bits!==$net_bits) echo 'Not in subnet';
else echo 'In subnet';

кроме того, если вы используете некоторую базу данных для хранения IPs, у нее уже могут быть все функции для их сравнения. Например, Postgres имеет тип inet и может определить, содержится ли IP в подсети следующим образом:

SELECT 
   '21DA:00D3:0000:2F3B:02AC:00FF:FE28:9C5A'::inet << 
   '21DA:00D3:0000:2F3B::/64'::inet;

9.11. Функции и операторы сетевых адресов в PostgreSQL


вы также можете использовать IpUtils класс symfony / http-foundation пакет:

IpUtils::checkIp6('2a01:8760:2:3001::1', '2a01:8760:2:3001::1/64')

это проверит действительность IPv6 и соответствие диапазона. Вернется false если это не так.


Я создал свое собственное решение, используя следующий код:

function iPv6MaskToByteArray($subnetMask) {
  $addr = str_repeat("f", $subnetMask / 4);
  switch ($subnetMask % 4) {
    case 0:
      break;
    case 1:
      $addr .= "8";
      break;
    case 2:
      $addr .= "c";
      break;
    case 3:
      $addr .= "e";
      break;
  }
  $addr = str_pad($addr, 32, '0');
  $addr = pack("H*" , $addr);
  return $addr;
}

function iPv6CidrMatch($address, $subnetAddress, $subnetMask) {
  $binMask = iPv6MaskToByteArray($subnetMask);
  return ($address & $binMask) == $subnetAddress;
}

обратите внимание, что $address и $subnetAddress были получены путем запуска строкового адреса через inet_pton. Вызовите функцию следующим образом:

$subnet = inet_pton("2001:06b8::");
$mask = 32;
$addr = inet_pton("2001:06b8:0000:0000:0000:0000:1428:07ab");
$match = iPv6CidrMatch($addr, $subnet, $mask); // TRUE

Если ваши маски всегда делятся на четыре (что довольно распространено в ipv6). Вы можете использовать:

function checkIPv6WithinRange($ipv6, $range) {
    list ($net, $mask) = preg_split("/\//", $range);

    if ($mask % 4)
        throw new NotImplementedException("Only masks divisible by 4 are supported");
    $stripChars = (128-$mask)/4;

    $hexNet = bin2hex(inet_pton($net));
    $reducedNet = substr($hexNet, 0, 0 - $stripChars);

    $hexIp = bin2hex(inet_pton($ipv6));
    $reducedIp = substr($hexIp, 0, 0 - $stripChars);

    return $reducedIp === $reducedNet;
}

вот пример, который работает, проверяя IP-адрес против списка отдельных IPs или CIDRs, как IPv4, так и IPv6:

https://gist.github.com/lyquix-owner/2620da22d927c99d57555530aab3279b

<?php
// IP to check
$ip_check = $_SERVER['REMOTE_ADDR'];

// Array of allowed IPs and subnets, both IPv4 and IPv6
$ips_allowed = array(
    '192.30.252.0/22'
    '2620:112:3000::/44'
    '192.168.16.104'
);

// Flag for IP match allowed list
$ip_match = false;

foreach($ips_allowed as $ip_allow) {
    // If IP has / means CIDR notation
    if(strpos($ip_allow, '/') === false) {
        // Check Single IP
        if(inet_pton($ip_check) == inet_pton($ip_allow)) {
            $allow = true;
            break;
        }
    }
    else {
        // Check IP range
        list($subnet, $bits) = explode('/', $ip_allow);

        // Convert subnet to binary string of $bits length
        $subnet = unpack('H*', inet_pton($subnet)); // Subnet in Hex
        foreach($subnet as $i => $h) $subnet[$i] = base_convert($h, 16, 2); // Array of Binary
        $subnet = substr(implode('', $subnet), 0, $bits); // Subnet in Binary, only network bits

        // Convert remote IP to binary string of $bits length
        $ip = unpack('H*', inet_pton($ip_check)); // IP in Hex
        foreach($ip as $i => $h) $ip[$i] = base_convert($h, 16, 2); // Array of Binary
        $ip = substr(implode('', $ip), 0, $bits); // IP in Binary, only network bits

        // Check network bits match
        if($subnet == $ip) {
            $allow = true;
            break;
        }
    }
}
if(!$allow) {
    die('IP not allowed');
}

в основном http://www.phpclasses.org/browse/file/70429.html

чтобы использовать его, просто вызовите

$cidr = new CIDR();
$cidr->match($ipv6, $ipv6_in_cidr);

результат "хорошим".