Есть ли хороший способ сопоставить адрес IPv6 с подсети IPv6 с использованием нотации CIDR? То, что я ищу, эквивалентно IPv6: сопоставление IP-маски с CIDR в PHP 5?
Приведенный выше пример не может быть использован, поскольку адрес IPv6 имеет длину 128 бит, что предотвращает правильную работу побитового сдвига слева. Можете ли вы подумать о другом?
EDIT: Добавлено мое собственное решение в список ответов.
Поскольку вы не можете конвертировать адреса IPv6 в integer, вы должны использовать биты, например:
$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';
Кроме того, если вы используете некоторую базу данных для хранения IP-адресов, возможно, у нее уже есть все функции для их сравнения. Например, Postgres имеет тип inet и может определить, содержится ли IP в подсети следующим образом:
SELECT '21DA:00D3:0000:2F3B:02AC:00FF:FE28:9C5A'::inet << '21DA:00D3:0000:2F3B::/64'::inet;
9,11. Функции и операторы сетевых адресов в PostgreSQL
Если ваши маски всегда делятся на четыре (что довольно часто встречается в 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; }
Вы также можете использовать класс 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
В основном http://www.phpclasses.org/browse/file/70429.html
Чтобы использовать его, просто позвоните
$cidr = new CIDR(); $cidr->match($ipv6, $ipv6_in_cidr);
Результат «Хорошо».
Вот пример, который работает, проверяя IP-адрес на список отдельных IP-адресов или CIDR, как 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'); }