Соответствует ли IPv4-адрес данному диапазону IP-адресов / маске?

Либо с PHP, либо с RegExp (или и тем и другим), как мне соответствовать диапазону IP-адресов?

Примеры входящих IP-адресов

10.210.12.12 10.253.12.12 10.210.12.254 10.210.12.95 10.210.12.60 

Диапазоны выборки

 10.210.12.0/24 10.210.12.0/16 10.210.*.* 10.*.*.* 

Я знаю, что могу это сделать:

 ?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?) 

… но он не учитывает диапазоны. Он просто позволяет вам сопоставлять входящий номер, чтобы узнать, является ли он IP-адресом, где каждый октет равен 0-255.

РЕДАКТИРОВАТЬ:

Есть также эта функция, которую я нашел в комментарии на php.net по функции ip2long.

 function ip_in_network($ip, $net_addr, $net_mask){ if($net_mask <= 0){ return false; } $ip_binary_string = sprintf("%032b",ip2long($ip)); $net_binary_string = sprintf("%032b",ip2long($net_addr)); return (substr_compare($ip_binary_string,$net_binary_string,0,$net_mask) === 0); } ip_in_network("192.168.2.1","192.168.2.0",24); //true ip_in_network("192.168.6.93","192.168.0.0",16); //true ip_in_network("1.6.6.6","128.168.2.0",1); //false 

Он короткий и сладкий, но не соответствует ситуации с звездочкой. Я также не знаю, полностью ли это, потому что он возвращает истинный результат, когда я думал, что это будет ложь:

 echo ip_in_network("192.168.2.1","192.167.0.0",1); 

… но, возможно, я не понимаю, что будет / 1. Возможно, мне нужно было использовать / 24.

Я адаптировал ответ php.net и сделал его лучше.

 function netMatch($network, $ip) { $network=trim($network); $orig_network = $network; $ip = trim($ip); if ($ip == $network) { echo "used network ($network) for ($ip)\n"; return TRUE; } $network = str_replace(' ', '', $network); if (strpos($network, '*') !== FALSE) { if (strpos($network, '/') !== FALSE) { $asParts = explode('/', $network); $network = @ $asParts[0]; } $nCount = substr_count($network, '*'); $network = str_replace('*', '0', $network); if ($nCount == 1) { $network .= '/24'; } else if ($nCount == 2) { $network .= '/16'; } else if ($nCount == 3) { $network .= '/8'; } else if ($nCount > 3) { return TRUE; // if *.*.*.*, then all, so matched } } echo "from original network($orig_network), used network ($network) for ($ip)\n"; $d = strpos($network, '-'); if ($d === FALSE) { $ip_arr = explode('/', $network); if (!preg_match("@\d*\.\d*\.\d*\.\d*@", $ip_arr[0], $matches)){ $ip_arr[0].=".0"; // Alternate form 194.1.4/24 } $network_long = ip2long($ip_arr[0]); $x = ip2long($ip_arr[1]); $mask = long2ip($x) == $ip_arr[1] ? $x : (0xffffffff << (32 - $ip_arr[1])); $ip_long = ip2long($ip); return ($ip_long & $mask) == ($network_long & $mask); } else { $from = trim(ip2long(substr($network, 0, $d))); $to = trim(ip2long(substr($network, $d+1))); $ip = ip2long($ip); return ($ip>=$from and $ip<=$to); } } function ech($b) { if ($b) { echo "MATCHED\n"; } else { echo "DID NOT MATCH\n"; } } echo "CLASS A TESTS\n"; ech(netMatch('10.168.1.0-10.168.1.100', '10.168.1.90')); ech(netMatch('10.168.*.*', '10.168.1.90')); ech(netMatch('10.168.0.0/16', '10.168.1.90')); ech(netMatch('10.169.1.0/24', '10.168.1.90')); ech(netMatch('10.168.1.90', '10.168.1.90')); echo "\nCLASS B TESTS\n"; ech(netMatch('130.168.1.0-130.168.1.100', '130.168.1.90')); ech(netMatch('130.168.*.*', '130.168.1.90')); ech(netMatch('130.168.0.0/16', '130.168.1.90')); ech(netMatch('130.169.1.0/24', '130.168.1.90')); ech(netMatch('130.168.1.90', '130.168.1.90')); echo "\nCLASS C TESTS\n"; ech(netMatch('192.168.1.0-192.168.1.100', '192.168.1.90')); ech(netMatch('192.168.*.*', '192.168.1.90')); ech(netMatch('192.168.0.0/16', '192.168.1.90')); ech(netMatch('192.169.1.0/24', '192.168.1.90')); ech(netMatch('192.168.1.90', '192.168.1.90')); echo "\nCLASS D TESTS\n"; ech(netMatch('230.168.1.0-230.168.1.100', '230.168.1.90')); ech(netMatch('230.168.*.*', '230.168.1.90')); ech(netMatch('230.168.0.0/16', '230.168.1.90')); ech(netMatch('230.169.1.0/24', '230.168.1.90')); ech(netMatch('230.168.1.90', '230.168.1.90')); echo "\nCLASS E TESTS\n"; ech(netMatch('250.168.1.0-250.168.1.100', '250.168.1.90')); ech(netMatch('250.168.*.*', '250.168.1.90')); ech(netMatch('250.168.0.0/16', '250.168.1.90')); ech(netMatch('250.169.1.0/24', '250.168.1.90')); ech(netMatch('250.168.1.90', '250.168.1.90')); 

Это приводит к:

 CLASS A TESTS from orig network (10.168.1.0-10.168.1.100) used network (10.168.1.0-10.168.1.100) for (10.168.1.90) MATCHED from orig network (10.168.*.*) used network (10.168.0.0/16) for (10.168.1.90) MATCHED from orig network (10.168.0.0/16) used network (10.168.0.0/16) for (10.168.1.90) MATCHED from orig network (10.169.1.0/24) used network (10.169.1.0/24) for (10.168.1.90) DID NOT MATCH used network (10.168.1.90) for (10.168.1.90) MATCHED CLASS B TESTS from orig network (130.168.1.0-130.168.1.100) used network (130.168.1.0-130.168.1.100) for (130.168.1.90) MATCHED from orig network (130.168.*.*) used network (130.168.0.0/16) for (130.168.1.90) MATCHED from orig network (130.168.0.0/16) used network (130.168.0.0/16) for (130.168.1.90) MATCHED from orig network (130.169.1.0/24) used network (130.169.1.0/24) for (130.168.1.90) DID NOT MATCH used network (130.168.1.90) for (130.168.1.90) MATCHED CLASS C TESTS from orig network (192.168.1.0-192.168.1.100) used network (192.168.1.0-192.168.1.100) for (192.168.1.90) MATCHED from orig network (192.168.*.*) used network (192.168.0.0/16) for (192.168.1.90) MATCHED from orig network (192.168.0.0/16) used network (192.168.0.0/16) for (192.168.1.90) MATCHED from orig network (192.169.1.0/24) used network (192.169.1.0/24) for (192.168.1.90) DID NOT MATCH used network (192.168.1.90) for (192.168.1.90) MATCHED CLASS D TESTS from orig network (230.168.1.0-230.168.1.100) used network (230.168.1.0-230.168.1.100) for (230.168.1.90) MATCHED from orig network (230.168.*.*) used network (230.168.0.0/16) for (230.168.1.90) MATCHED from orig network (230.168.0.0/16) used network (230.168.0.0/16) for (230.168.1.90) MATCHED from orig network (230.169.1.0/24) used network (230.169.1.0/24) for (230.168.1.90) DID NOT MATCH used network (230.168.1.90) for (230.168.1.90) MATCHED CLASS E TESTS from orig network (250.168.1.0-250.168.1.100) used network (250.168.1.0-250.168.1.100) for (250.168.1.90) MATCHED from orig network (250.168.*.*) used network (250.168.0.0/16) for (250.168.1.90) MATCHED from orig network (250.168.0.0/16) used network (250.168.0.0/16) for (250.168.1.90) MATCHED from orig network (250.169.1.0/24) used network (250.169.1.0/24) for (250.168.1.90) DID NOT MATCH used network (250.168.1.90) for (250.168.1.90) MATCHED 

Преобразование в 32-битное беззнаковое и использование булевых / побитовых операций.

Например, преобразуйте 192.168.25.1 в 0xC0A81901.

Затем вы можете увидеть, совпадает ли она с маской 192.168.25 / 24, преобразованием десятичной части маски, то есть 0xC0A81900, и создания 24-битной маски, то есть 0xFFFFFF00.

Выполните поразрядное И между указанным адресом и маской и сравните с пунктирной десятичной частью спецификации маски. Например,

 0xC0A81901 AND 0xFFFFFF00 ==> 0xC0A81900 (result) compare 0xC0A81900 (result) to 0xC0A81900. 

Я не знаю PHP, но google говорит мне, что PHP имеет inet_pton (), что я бы использовал в C, чтобы выполнить преобразование из десятичной точки в n-бит без знака. См. http://php.net/manual/en/function.inet-pton.php

Я улучшил приведенный выше пример (у меня есть сетевая маска с / 29, так что она не работает).

 function check_netmask($mask, $ip) { @list($net, $bits) = explode('/', $mask); $bits = isset($bits) ? $bits : 32; $bitmask = -pow(2, 32-$bits) & 0x00000000FFFFFFFF; $netmask = ip2long($net) & $bitmask; $ip_bits = ip2long($ip) & $bitmask; return (($netmask ^ $ip_bits) == 0); } 

Если вы хотите увидеть его в действии, добавьте следующее:

 print("IP Bits: " . str_pad(decbin(ip2long($ip)), 32, '0', STR_PAD_LEFT)); print "\n"; print("Bitmask: " . str_pad(decbin($bitmask), 32, '0', STR_PAD_LEFT)); print "\n"; print("Netmask: " . str_pad(decbin($netmask), 32, '0', STR_PAD_LEFT)); print "\n"; print("Match: " . str_pad(decbin($netmask ^ $ip_bits), 32, '0', STR_PAD_LEFT)); print "\n"; 

Запустите его с чем-то вроде этого:

 print var_dump(check_netmask($argv[1], $argv[2])); 

Regex действительно не похоже на правильный инструмент для работы с масками подсети (по крайней мере, не в десятичной форме). Это можно сделать, но это будет уродливо.

Я настоятельно рекомендую разбить строку на 4 целых числа, объединив их с 32-битным int, а затем используя стандартные побитовые операции (в основном побитно-AND, а затем сравнение).

Используйте strpos для их сопоставления как строки.

 <?php $ips = array(); $ips[0] = "10.210.12.12"; $ips[1] = "10.253.12.12"; $ips[2] = "10.210.12.254"; $ips[3] = "10.210.12.95"; $ips[4] = "10.210.12.60"; $matches = array(); foreach($ips as $ip){ if(strpos($ip, "10.253.") === 0){ $matches[] = $ip; } } print_r($matches); ?>