Mapping IP mask to CIDR in PHP 5?

I am looking for a quick / easy method for matching a given IP4 with a clear IP address with a CIDR notation mask.

I have a bunch of IP addresses, I need to see if they match the range of IP addresses.

Example:

$ips = array('10.2.1.100', '10.2.1.101', '10.5.1.100', '1.2.3.4'); foreach ($ips as $IP) { if (cidr_match($IP, '10.2.0.0/16') == true) { print "you're in the 10.2 subnet\n"; } } 

What does cidr_match() look like?

It should not be easy, but it will be good quickly. Everything that uses only the built-in / general functions is a bonus (since I will probably get one person to show me something in the pear that does this, but I can not depend on the pear or the package that is installed there, where my code is deployed).

+46
php cidr
Feb 27 '09 at 9:36
source share
12 answers

If only IPv4 is used:

  • use ip2long() to convert IP addresses and subnet range to long integers.
  • convert / xx to subnet mask
  • execute bitwise and "(for example, ip and mask)" and check that it is "result = subnet"

something like this should work:

 function cidr_match($ip, $range) { list ($subnet, $bits) = explode('/', $range); $ip = ip2long($ip); $subnet = ip2long($subnet); $mask = -1 << (32 - $bits); $subnet &= $mask; # nb: in case the supplied subnet wasn't correctly aligned return ($ip & $mask) == $subnet; } 
+59
Feb 27 '09 at 9:48
source share

I found that many of these methods break after PHP 5.2. However, the following solution works in versions 5.2 and higher:

 function cidr_match($ip, $cidr) { list($subnet, $mask) = explode('/', $cidr); if ((ip2long($ip) & ~((1 << (32 - $mask)) - 1) ) == ip2long($subnet)) { return true; } return false; } 

Results Examples

 cidr_match ("1.2.3.4", "0.0.0.0/0"): true
 cidr_match ("127.0.0.1", "127.0.0.1/32"): true
 cidr_match ("127.0.0.1", "127.0.0.2/32"): false

Source http://www.php.net/manual/en/function.ip2long.php#82397 .

+36
Feb 12
source share

In a similar situation, I ended up using symfony / http-foundation.

When using this package, your code will look like this:

 $ips = array('10.2.1.100', '10.2.1.101', '10.5.1.100', '1.2.3.4'); foreach($ips as $IP) { if (\Symfony\Component\HttpFoundation\IpUtils::checkIp($_SERVER['REMOTE_ADDR'], '10.2.0.0/16')) { print "you're in the 10.2 subnet\n"; } } 

It also handles IPv6.

Link: https://packagist.org/packages/symfony/http-foundation

+34
Jan 14 '15 at 13:13
source share

Changed some function:

  • split with explode



 function cidr_match($ip, $range) { list ($subnet, $bits) = explode('/', $range); $ip = ip2long($ip); $subnet = ip2long($subnet); $mask = -1 << (32 - $bits); $subnet &= $mask; return ($ip & $mask) == $subnet; } 
+3
Dec 17 '12 at 8:32
source share

My technique uses bit-bit using subnet and mask.

 function cidr_match($ip, $range){ list ($subnet, $bits) = explode('/', $range); $ip = substr(IP2bin($ip),0,$bits) ; $subnet = substr(IP2Bin($subnet),0,$bits) ; return ($ip == $subnet) ; } function IP2Bin($ip){ $ips = explode(".",$ip) ; foreach ($ips as $iptmp){ $ipbin .= sprintf("%08b",$iptmp) ; } return $ipbin ; } 
+2
Jan 26 '13 at 10:00
source share

I also needed to test IP against the CIDR mask. I found a site with great explanation and source code that works great.

Website http://pgregg.com/blog/2009/04/php-algorithms-determining-if-an-ip-is-within-a-specific-range/

Since the website may one day cease to exist, here is the code

 <?php /* * ip_in_range.php - Function to determine if an IP is located in a * specific range as specified via several alternative * formats. * * Network ranges can be specified as: * 1. Wildcard format: 1.2.3.* * 2. CIDR format: 1.2.3/24 OR 1.2.3.4/255.255.255.0 * 3. Start-End IP format: 1.2.3.0-1.2.3.255 * * Return value BOOLEAN : ip_in_range($ip, $range); * * Copyright 2008: Paul Gregg <pgregg@pgregg.com> * 10 January 2008 * Version: 1.2 * * Source website: http://www.pgregg.com/projects/php/ip_in_range/ * Version 1.2 * * This software is Donationware - if you feel you have benefited from * the use of this tool then please consider a donation. The value of * which is entirely left up to your discretion. * http://www.pgregg.com/donate/ * * Please do not remove this header, or source attibution from this file. */ // decbin32 // In order to simplify working with IP addresses (in binary) and their // netmasks, it is easier to ensure that the binary strings are padded // with zeros out to 32 characters - IP addresses are 32 bit numbers Function decbin32 ($dec) { return str_pad(decbin($dec), 32, '0', STR_PAD_LEFT); } // ip_in_range // This function takes 2 arguments, an IP address and a "range" in several // different formats. // Network ranges can be specified as: // 1. Wildcard format: 1.2.3.* // 2. CIDR format: 1.2.3/24 OR 1.2.3.4/255.255.255.0 // 3. Start-End IP format: 1.2.3.0-1.2.3.255 // The function will return true if the supplied IP is within the range. // Note little validation is done on the range inputs - it expects you to // use one of the above 3 formats. Function ip_in_range($ip, $range) { if (strpos($range, '/') !== false) { // $range is in IP/NETMASK format list($range, $netmask) = explode('/', $range, 2); if (strpos($netmask, '.') !== false) { // $netmask is a 255.255.0.0 format $netmask = str_replace('*', '0', $netmask); $netmask_dec = ip2long($netmask); return ( (ip2long($ip) & $netmask_dec) == (ip2long($range) & $netmask_dec) ); } else { // $netmask is a CIDR size block // fix the range argument $x = explode('.', $range); while(count($x)<4) $x[] = '0'; list($a,$b,$c,$d) = $x; $range = sprintf("%u.%u.%u.%u", empty($a)?'0':$a, empty($b)?'0':$b,empty($c)?'0':$c,empty($d)?'0':$d); $range_dec = ip2long($range); $ip_dec = ip2long($ip); # Strategy 1 - Create the netmask with 'netmask' 1s and then fill it to 32 with 0s #$netmask_dec = bindec(str_pad('', $netmask, '1') . str_pad('', 32-$netmask, '0')); # Strategy 2 - Use math to create it $wildcard_dec = pow(2, (32-$netmask)) - 1; $netmask_dec = ~ $wildcard_dec; return (($ip_dec & $netmask_dec) == ($range_dec & $netmask_dec)); } } else { // range might be 255.255.*.* or 1.2.3.0-1.2.3.255 if (strpos($range, '*') !==false) { // ab*.* format // Just convert to AB format by setting * to 0 for A and 255 for B $lower = str_replace('*', '0', $range); $upper = str_replace('*', '255', $range); $range = "$lower-$upper"; } if (strpos($range, '-')!==false) { // AB format list($lower, $upper) = explode('-', $range, 2); $lower_dec = (float)sprintf("%u",ip2long($lower)); $upper_dec = (float)sprintf("%u",ip2long($upper)); $ip_dec = (float)sprintf("%u",ip2long($ip)); return ( ($ip_dec>=$lower_dec) && ($ip_dec<=$upper_dec) ); } echo 'Range argument is not in 1.2.3.4/24 or 1.2.3.4/255.255.255.0 format'; return false; } } ?> 

(I did not develop it, it is designed by Paul Gregg ( http://pgregg.com/ )

+2
Jan 30 '14 at 16:52
source share

Here is one quick 64 bit function to do this, comment out the return line you don't need. Accepting any valid IPv4 with or without a valid CIDR routing prefix, e.g. 63.161.156.0/24 or 63.161.156.0

 <?php function cidr2range($ipv4){ if ($ip=strpos($ipv4,'/')) {$n_ip=(1<<(32-substr($ipv4,1+$ip)))-1; $ip_dec=ip2long(substr($ipv4,0,$ip)); } else {$n_ip=0; $ip_dec=ip2long($ipv4); } $ip_min=$ip_dec&~$n_ip; $ip_max=$ip_min+$n_ip; #Array(2) of Decimal Values Range return [$ip_min,$ip_max]; #Array(2) of Ipv4 Human Readable Range return [long2ip($ip_min),long2ip($ip_max)]; #Array(2) of Ipv4 and Subnet Range return [long2ip($ip_min),long2ip(~$n_ip)]; #Array(2) of Ipv4 and Wildcard Bits return [long2ip($ip_min),long2ip($n_ip)]; #Integer Number of Ipv4 in Range return ++$n_ip; } 

To quickly check if a given ipv4 matches a given CIDR , it can be embedded as in this example

 <?php $given_cidr='55.55.55.0/24'; $given_ipv4='55.55.55.55'; if(($range=cidr2range($given_cidr)) && ($check=ip2long($given_ipv4))!==false && $check>=$range[0] && $check<=$range[1]) { echo 'Yes, '.$given_ipv4.' is included in '.$given_cidr; } else { echo 'No, '.$given_ipv4.' is not included in '.$given_cidr; } 

To get the full range as an array for a given IP address (with or without CIDR routing prefix), you can use the following code, but be careful because, for example, 25.25.25.25/16 returns an array with 65536 elements, and you you can easily run out of memory using a smaller routing prefix

 <?php $result=cidr2range($ipv4); for($ip_dec=$result[0];$ip_dec<=$result[1];$ip_dec++) $full_range[$ip_dec]=long2ip($ip_dec); print_r($full_range); 

To check if a given ipv4 matches a given IP array (with or without CIDR routing prefix)

 <?php #This code is checking if a given ip belongs to googlebot $given_ipv4='74.125.61.208'; $given_cidr_array=['108.59.93.43/32','108.59.93.40/31','108.59.93.44/30','108.59.93.32/29','108.59.93.48/28','108.59.93.0/27','108.59.93.64/26','108.59.93.192/26','108.59.92.192/27','108.59.92.128/26','108.59.92.96/27','108.59.92.0/27','108.59.94.208/29','108.59.94.192/28','108.59.94.240/28','108.59.94.128/26','108.59.94.16/29','108.59.94.0/28','108.59.94.32/27','108.59.94.64/26','108.59.95.0/24','108.59.88.0/22','108.59.81.0/27','108.59.80.0/24','108.59.82.0/23','108.59.84.0/22','108.170.217.128/28','108.170.217.160/27','108.170.217.192/26','108.170.217.0/25','108.170.216.0/24','108.170.218.0/23','108.170.220.0/22','108.170.208.0/21','108.170.192.0/20','108.170.224.0/19','108.177.0.0/17','104.132.0.0/14','104.154.0.0/15','104.196.0.0/14','107.167.160.0/19','107.178.192.0/18','125.17.82.112/30','125.16.7.72/30','74.125.0.0/16','72.14.192.0/18','77.109.131.208/28','77.67.50.32/27','66.102.0.0/20','66.227.77.144/29','66.249.64.0/19','67.148.177.136/29','64.124.98.104/29','64.71.148.240/29','64.68.64.64/26','64.68.80.0/20','64.41.221.192/28','64.41.146.208/28','64.9.224.0/19','64.233.160.0/19','65.171.1.144/28','65.170.13.0/28','65.167.144.64/28','65.220.13.0/24','65.216.183.0/24','70.32.132.0/23','70.32.128.0/22','70.32.136.0/21','70.32.144.0/20','85.182.250.128/26','85.182.250.0/25','80.239.168.192/26','80.149.20.0/25','61.246.224.136/30','61.246.190.124/30','63.237.119.112/29','63.226.245.56/29','63.158.137.224/29','63.166.17.128/25','63.161.156.0/24','63.88.22.0/23','41.206.188.128/26','12.234.149.240/29','12.216.80.0/24','8.34.217.24/29','8.34.217.0/28','8.34.217.32/27','8.34.217.64/26','8.34.217.128/25','8.34.216.0/24','8.34.218.0/23','8.34.220.0/22','8.34.208.128/29','8.34.208.144/28','8.34.208.160/27','8.34.208.192/26','8.34.208.0/25','8.34.209.0/24','8.34.210.0/23','8.34.212.0/22','8.35.195.128/28','8.35.195.160/27','8.35.195.192/26','8.35.195.0/25','8.35.194.0/24','8.35.192.0/23','8.35.196.0/22','8.35.200.0/21','8.8.8.0/24','8.8.4.0/24','8.6.48.0/21','4.3.2.0/24','23.236.48.0/20','23.251.128.0/19','216.239.32.0/19','216.252.220.0/22','216.136.145.128/27','216.33.229.160/29','216.33.229.144/29','216.34.7.176/28','216.58.192.0/19','216.109.75.80/28','216.74.130.48/28','216.74.153.0/27','217.118.234.96/28','208.46.199.160/29','208.44.48.240/29','208.21.209.0/28','208.184.125.240/28','209.185.108.128/25','209.85.128.0/17','213.200.103.128/26','213.200.99.192/26','213.155.151.128/26','199.192.112.224/29','199.192.112.192/27','199.192.112.128/26','199.192.112.0/25','199.192.113.176/28','199.192.113.128/27','199.192.113.192/26','199.192.113.0/25','199.192.115.80/28','199.192.115.96/27','199.192.115.0/28','199.192.115.128/25','199.192.114.192/26','199.192.114.0/25','199.223.232.0/21','198.108.100.192/28','195.16.45.144/29','192.104.160.0/23','192.158.28.0/22','192.178.0.0/15','206.160.135.240/28','207.223.160.0/20','203.222.167.144/28','173.255.125.72/29','173.255.125.80/28','173.255.125.96/27','173.255.125.0/27','173.255.125.128/25','173.255.124.240/29','173.255.124.232/29','173.255.124.192/27','173.255.124.128/29','173.255.124.144/28','173.255.124.160/27','173.255.124.48/29','173.255.124.32/28','173.255.124.0/27','173.255.124.64/26','173.255.126.0/23','173.255.122.128/26','173.255.122.64/26','173.255.123.0/24','173.255.121.128/26','173.255.121.0/25','173.255.120.0/24','173.255.117.32/27','173.255.117.64/26','173.255.117.128/25','173.255.116.192/27','173.255.116.128/26','173.255.116.0/25','173.255.118.0/23','173.255.112.0/22','173.194.0.0/16','172.102.8.0/21','172.253.0.0/16','172.217.0.0/16','162.216.148.0/22','162.222.176.0/21','180.87.33.64/26','128.177.109.0/26','128.177.119.128/25','128.177.163.0/25','130.211.0.0/16','142.250.0.0/15','146.148.0.0/17']; echo '<pre>'; $in_range=false; if (($given_ipv4_dec=ip2long($given_ipv4))!==false) { foreach($given_cidr_array as $given_cidr){ if(($range=cidr2range($given_cidr)) && $given_ipv4_dec>=$range[0] && $given_ipv4_dec<=$range[1]) { $in_range=true; echo $given_ipv4.' matched '.$given_cidr.' ('.join(array_map('long2ip',$range),' - ').")\n"; } } } echo $given_ipv4.' is probably'.($in_range?'':' not').' a Googlebot IP'; 

For quick start, the function does not check the input, but formally it should be a string corresponding to the following regular expression

 #^(?:((?:0)|(?:2(?:(?:[0-4][0-9])|(?:5[0-5])))|(?:1?[0-9]{1,2}))\.((?:0)|(?:2(?:(?:[0-4][0-9])|(?:5[0-5])))|(?:1?[0-9]{1,2}))\.((?:0)|(?:2(?:(?:[0-4][0-9])|(?:5[0-5])))|(?:1?[0-9]{1,2}))\.((?:0)|(?:2(?:(?:[0-4][0-9])|(?:5[0-5])))|(?:1?[0-9]{1,2}))(?:/((?:(?:0)|(?:3[0-2])|(?:[1-2]?[0-9]))))?)$# 

If you want to check the input before using the function

 <?php if (is_string($ipv4) && preg_match('#^(?:((?:0)|(?:2(?:(?:[0-4][0-9])|(?:5[0-5])))|(?:1?[0-9]{1,2}))\.((?:0)|(?:2(?:(?:[0-4][0-9])|(?:5[0-5])))|(?:1?[0-9]{1,2}))\.((?:0)|(?:2(?:(?:[0-4][0-9])|(?:5[0-5])))|(?:1?[0-9]{1,2}))\.((?:0)|(?:2(?:(?:[0-4][0-9])|(?:5[0-5])))|(?:1?[0-9]{1,2}))(?:/((?:(?:0)|(?:3[0-2])|(?:[1-2]?[0-9]))))?)$#',$ipv4)) { #This is a valid ipv4 with or without CIDR Routing Prefix $result=cidr2range($ipv4); print_r($result); } 

Then the formal answer to your question is as follows

 <?php #Requiring cidr2range shown above function function cidr_match($mixed_ip,$mixed_cidr){ if (!is_array($mixed_ip)){ $string_mode=true; $mixed_ip=[$mixed_ip=>0]; } else $mixed_ip=array_fill_keys($mixed_ip,0); if (!is_array($mixed_cidr)) $mixed_cidr=[$mixed_cidr]; foreach($mixed_ip as $ip => &$result) foreach($mixed_cidr as $cidr) { if(($range=cidr2range($cidr)) && ($check=ip2long($ip))!==false && $check>=$range[0] && $check<=$range[1]){ $result=$cidr; break; } } $mixed_ip=array_filter($mixed_ip); return $string_mode?($mixed_ip?true:false):$mixed_ip; } print '<pre>'; #Your example $ips = array('10.2.1.100', '10.2.1.101', '10.5.1.100', '1.2.3.4'); foreach ($ips as $IP) { if (cidr_match($IP, '10.2.0.0/16') == true) { print "you're in the 10.2 subnet\n"; } } #Also working with IP array and/or CIDR array #If IP array is given then return an array containing IP (keys) matching CIDR (values) $result=cidr_match($ips,['20.2.0.0/16','10.2.0.0/15']); foreach($result as $ip => $cidr){ print "$ip is in the $cidr subnet\n"; } 

You can compile your own function using these examples, hope these few lines have helped you ...

+2
Feb 16 '17 at 13:58 on
source share
 function cidr_match($ipStr, $cidrStr) { $ip = ip2long($ipStr); $cidrArr = split('/',$cidrStr); $maskIP = ip2long($cidrArr[0]); $maskBits = 32 - $cidrArr[1]; return (($ip>>$maskBits) == ($maskIP>>$maskBits)); } 
+1
Feb 27 '09 at 9:54
source share

Just a note, Alnitak's answer works 32/64 bit.

Here is the prepared version, for quick protection against spam based on lists of IP addresses of the country, which you can get everywhere. google for country ip list or country ip block (Have one here, it’s very difficult to find it in this page navigation page: Country ip block generator )

Copy paste the cidr ip list into the $ cidrs line. And put this code directly in front of the html page, possibly in the header.php file.

It can also be used to filter the use of AdSense in page templates by country.

This decision is only in the middle of the night. Sometimes I need to quickly come up with something like this for a client, so here it is.

 //++++++++++++++++++++++ //COUNTRY SPAM PROTECTOR //speed: ~5ms @ 2000 cidrs //comments start with # //++++++++++++++++++++++ $cidrs= ' #yourcountry 1.3.4.5/21 #mycountry 6.7.8.9/20 '; //$cidrs.="\n".'123.12.12.12/32';//test, your ip $cidrs_ar=preg_split('/\s+/',$cidrs,-1,PREG_SPLIT_NO_EMPTY); $ip=@$_SERVER['REMOTE_ADDR']; $iplong=ip2long($ip); //var_export($cidrs_ar);var_export($ip);var_export($iplong); if($iplong) foreach($cidrs_ar as $cidr) { $ar=explode ('/', $cidr); $netiplong=ip2long($ar[0]); if($netiplong===false) continue; $mask=intval(@$ar[1]); if(!$mask) continue; $bitmask=-1 <<(32-$mask); if(($iplong & $bitmask) == ($netiplong & $bitmask)) { header('Location: http://www.someotherwebsite.com/',true,303); exit; } } 
+1
Mar 27 '13 at 11:39
source share

You can also use the Net_IPv4 PEAR library .

 function cidr_match($ip, $net){ include_once("Net/IPv4.php"); $objIP = new Net_IPv4(); return $objIP->ipInNetwork($ip, $net); } 
+1
Aug 16 '13 at 7:42 on
source share

Perhaps this is useful to someone.

Convert bitmask to IP mask:

 // convert 12 => 255.240.0.0 // ip2long('255.255.255.255') == -1 $ip = long2ip((-1 << (32 - $bit)) & -1); 

Convert IP mask to bit mask:

 // convert 255.240.0.0 => 12 // is valid IP if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) === false) { throw new \InvalidArgumentException(sprintf('Invalid IP "%s".', $ip)); } // convert decimal to binary $mask = ''; foreach (explode('.', $ip) as $octet) { $mask .= str_pad(decbin($octet), 8, '0', STR_PAD_LEFT); } // check mask if (strpos('01', $mask) !== false) { // valid 11111111111111111111111100000000 -> 255.255.255.0 // invalid 11111111111111111111111100000001 -> 255.255.255.1 throw new \InvalidArgumentException(sprintf('IP mask "%s" is not valid.', $ip)); } $bit = substr_count($mask, '1'); // bit mask 
+1
Dec 14 '16 at 16:26
source share

I want you to take a look at my few lines. The examples that people offered in front of me do not seem to work. One of the reasons, in my opinion, is that the bits of the CIDR mask are binary numbers, so the shift of the bits must be performed by a binary number. I tried converting a long IP to binary, but ran into a maximum binary number. Ok, here are my few lines ... I'm waiting for your comments.

 function cidr_match($ipStr, $cidrStr) { $ipStr = explode('.', $ipStr); foreach ($ipStr as $key => $val) { $ipStr[$key] = str_pad(decbin($val), 8, '0', STR_PAD_LEFT); } $ip = ''; foreach ($ipStr as $binval) { $ip = $ip . $binval; } $cidrArr = explode('/',$cidrStr); $maskIP = explode('.', $cidrArr[0]); foreach ($maskIP as $key => $val) { $maskIP[$key] = str_pad(decbin($val), 8, '0', STR_PAD_LEFT); } $maskIP = ''; foreach ($ipStr as $binval) { $maskIP = $maskIP . $binval; } $maskBits = 32 - $cidrArr[1]; return (($ip>>$maskBits) == ($maskIP>>$maskBits)); } 
0
Jun 03 '09 at 23:15
source share



All Articles