Getting list IPs from CIDR notation in PHP

28.9k views Asked by At

Is there a way (or function/class) to get the list of IP addresses from a CIDR notation?

For example, I have 73.35.143.32/27 CIDR and want to get the list of all IP's in this notation. Any suggestions?

Thank you.

14

There are 14 answers

3
jonavon On BEST ANSWER

I'll edit the aforementioned class to contain a method for that. Here is the code I came up with that might help you until then.

function cidrToRange($cidr) {
  $range = array();
  $cidr = explode('/', $cidr);
  $range[0] = long2ip((ip2long($cidr[0])) & ((-1 << (32 - (int)$cidr[1]))));
  $range[1] = long2ip((ip2long($range[0])) + pow(2, (32 - (int)$cidr[1])) - 1);
  return $range;
}
var_dump(cidrToRange("73.35.143.32/27"));

//////////////////OUTPUT////////////////////////
// array(2) {
//   [0]=>
//   string(12) "73.35.143.32"
//   [1]=>
//   string(12) "73.35.143.63"
// }
/////////////////////////////////////////////////

Returns the low end of the ip range as the first entry in the array, then returns the high end as the second entry.

1
Mark Baker On

I don't believe this class will return a list of IPs, but it does provide some useful methods for working with CIDR blocks.

1
Piskvor left the building On

Well, it's a bitmask - 73.35.143.32/27 means that 27 bits are the network mask, and the rest is available for assigning to the nodes in the network:

73.35.143.32

in binary is this (dots shown for legibility):

01001001.00100011.10001111.00100000

The netmask is 27 bits:

11111111.11111111.11111111.11100000

So you can just AND them together and get this:

01001001.00100011.10001111.001 00000
 network prefix (27 bits)     | node address (5 bits)

From here, you can just enumerate all the combinations in the node address (00000 is 0, 11111 is 31, so a simple loop is enough), and you'll have all the available hosts.

Converting this pseudocode to PHP is left as an exercise to the reader ;)

Oh, and the obligatory deprecation warning: IPv4 is now full, consider also IPv6.

2
Kosmonaft On

Hej. I also needed this function and I edit 1 other to return me list of all IP addresses from range. This will return list of IP addresses from CIDR Notation. Enjoy it ;)

<?php 

    $ip_addr_cidr = "192.256.0.0/16";
    $ip_arr = explode('/', $ip_addr_cidr);

    $bin = '';
    for($i=1;$i<=32;$i++) {
        $bin .= $ip_arr[1] >= $i ? '1' : '0';
    }
    $ip_arr[1] = bindec($bin);

    $ip = ip2long($ip_arr[0]);
    $nm = ip2long($ip_arr[1]);
    $nw = ($ip & $nm);
    $bc = $nw | (~$nm);

    echo "Number of Hosts:    " . ($bc - $nw - 1) . "<br/>";
    echo "Host Range:         " . long2ip($nw + 1) . " -> " . long2ip($bc - 1) . "<br/>". "<br/>";

    for($zm=1;($nw + $zm)<=($bc - 1);$zm++)
        {
        echo long2ip($nw + $zm).  "<br/>";
        }

    ?>
0
Randy Lam On

Highly recommend this PHP Lib: https://github.com/S1lentium/IPTools

It can manipulate network easily with many functions.

e.g. Iterate over Network IP adresses:

$network = Network::parse('192.168.1.0/24');
foreach($network as $ip) {
    echo (string)$ip . PHP_EOL;
}

// output:
192.168.1.0
...
192.168.1.255
2
Filip On

There is small fix - when someone place ip prefix like: 127.0.0.15/26 this function returns bad last IP adress. In this function is at line 4 (starts: $range[1]...) changed $cidr[0] to $range[0] - now its return last IP address good for every IP in range.

function cidrToRange($cidr) {
  $range = array();
  $cidr = explode('/', $cidr);
  $range[0] = long2ip((ip2long($cidr[0])) & ((-1 << (32 - (int)$cidr[1]))));
  $range[1] = long2ip((ip2long($range[0])) + pow(2, (32 - (int)$cidr[1])) - 1);
  return $range;
}
var_dump(cidrToRange("127.0.0.15/26"));

Before fix:

array(2) {
  [0]=>
  string(9) "127.0.0.0"
  [1]=>
  string(10) "127.0.0.78"
}
string(41) "127.0.0.15/26 >> 2130706432 -> 2130706510"

After fix:

array(2) {
  [0]=>
  string(9) "127.0.0.0"
  [1]=>
  string(10) "127.0.0.63"
}
string(41) "127.0.0.15/26 >> 2130706432 -> 2130706495"
0
Top-Master On

I previously wrote an entire Class which handled this task pretty well (for another Language), and thanks to PHP's ip2long(...) method, we don't need to calculate manually in PHP.

But my Unit-Test data can be used to test any of the other answers:

<?php
// Below has "CIDR = Minimum ... Maximum" format.
$validRangeMap = [
    '0.0.0.0/1=0.0.0.0...127.255.255.255',
    '128.0.0.0/2=128.0.0.0...191.255.255.255',
    '192.0.0.0/9=192.0.0.0...192.127.255.255',
    '192.128.0.0/11=192.128.0.0...192.159.255.255',
    '192.160.0.0/13=192.160.0.0...192.167.255.255',
    '192.168.0.0/19=192.168.0.0...192.168.31.255',
    '192.168.32.0/21=192.168.32.0...192.168.39.255',
    '192.168.40.0/23=192.168.40.0...192.168.41.255',
    '192.168.42.0/23=192.168.42.0...192.168.43.255',
    '192.168.44.0/22=192.168.44.0...192.168.47.255',
    '192.168.48.0/20=192.168.48.0...192.168.63.255',
    '192.168.64.0/18=192.168.64.0...192.168.127.255',
    '192.168.128.0/17=192.168.128.0...192.168.255.255',
    '192.169.0.0/16=192.169.0.0...192.169.255.255',
    '192.170.0.0/15=192.170.0.0...192.171.255.255',
    '192.172.0.0/14=192.172.0.0...192.175.255.255',
    '192.176.0.0/12=192.176.0.0...192.191.255.255',
    '192.192.0.0/10=192.192.0.0...192.255.255.255',
    '193.0.0.0/8=193.0.0.0...193.255.255.255',
    '194.0.0.0/7=194.0.0.0...195.255.255.255',
    '196.0.0.0/6=196.0.0.0...199.255.255.255',
    '200.0.0.0/5=200.0.0.0...207.255.255.255',
    '208.0.0.0/4=208.0.0.0...223.255.255.255',
    '224.0.0.0/4=224.0.0.0...239.255.255.255',
    '240.0.0.0/5=240.0.0.0...247.255.255.255',
    '248.0.0.0/6=248.0.0.0...251.255.255.255',
    '252.0.0.0/7=252.0.0.0...253.255.255.255',
    '254.0.0.0/8=254.0.0.0...254.255.255.255',
    '255.0.0.0/9=255.0.0.0...255.127.255.255',
    '255.128.0.0/10=255.128.0.0...255.191.255.255',
    '255.192.0.0/11=255.192.0.0...255.223.255.255',
    '255.224.0.0/12=255.224.0.0...255.239.255.255',
    '255.240.0.0/13=255.240.0.0...255.247.255.255',
    '255.248.0.0/14=255.248.0.0...255.251.255.255',
    '255.252.0.0/15=255.252.0.0...255.253.255.255',
    '255.254.0.0/16=255.254.0.0...255.254.255.255',
    '255.255.0.0/17=255.255.0.0...255.255.127.255',
    '255.255.128.0/18=255.255.128.0...255.255.191.255',
    '255.255.192.0/19=255.255.192.0...255.255.223.255',
    '255.255.224.0/20=255.255.224.0...255.255.239.255',
    '255.255.240.0/21=255.255.240.0...255.255.247.255',
    '255.255.248.0/22=255.255.248.0...255.255.251.255',
    '255.255.252.0/23=255.255.252.0...255.255.253.255',
    '255.255.254.0/24=255.255.254.0...255.255.254.255',
    '255.255.255.0/25=255.255.255.0...255.255.255.127',
    '255.255.255.128/26=255.255.255.128...255.255.255.191',
    '255.255.255.192/27=255.255.255.192...255.255.255.223',
    '255.255.255.224/28=255.255.255.224...255.255.255.239',
    '255.255.255.240/29=255.255.255.240...255.255.255.247',
    '255.255.255.248/30=255.255.255.248...255.255.255.251',
    '255.255.255.252/31=255.255.255.252...255.255.255.253',
    '255.255.255.254/32=255.255.255.254...255.255.255.254',
];

Example

We can test Jonavon's answer, like:

foreach ($validRangeMap as $entry) {
    $expected = preg_split('/(=|(\.\.\.))+/', $entry);
    $actual = cidrToRange($expected[0]);

    if ($actual[0] !== $expected[1]) {
        echo "Test-Failed! '$actual[0]' should be equal '$expected[1]' <br>" . PHP_EOL;
    } else if ($actual[1] !== $expected[2]) {
        echo "Test-Failed! '$actual[1]' should be equal '$expected[2]' <br>" . PHP_EOL;
    } else {
        echo "Tested '$expected[0]' CIDR <br>" . PHP_EOL;
    }
}

function cidrToRange($cidr) {
  $range = array();
  $cidr = explode('/', $cidr);
  $range[0] = long2ip((ip2long($cidr[0])) & ((-1 << (32 - (int)$cidr[1]))));
  $range[1] = long2ip((ip2long($range[0])) + pow(2, (32 - (int)$cidr[1])) - 1);
  return $range;
}

Note that it passed the tests.

3
Php'Regex On

Here is one fast 64bits function to do it, please comment the return line you don't need. Accepting any valid Ipv4 with or without valid CIDR Routing Prefix for example 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 run fast the function don't check input but formally it should be a string matching the following regex

#^(?:((?: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 verify 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);
}

To get the full range as an array for a given IP (with or without CIDR Routing Prefix) you can use the following code but be carefull because for example 25.25.25.25/16 return an array with 65536 elements and 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 fast check if a given ipv4 is matching a given CIDR you can do it inline like 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 fast check if a given ipv4 is matching a given array of IP (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';

hope that these few lines have helped you…

2
AndyC On

I had some problems with the previous versions, various PHP errors like undefined offset etc.

I found what I think is a better way to do it without the errors as detailed below.

This takes IPV4 CIDR notation IP addresses and can be used to get the start/end IP addresses of the CIDR range or count the total number of IP addresses in a database of IP addresses.

    $count = 0;
    $result = $sia_db_con->query("SELECT `ip_address` FROM `ip4` WHERE `ip_ban_active`='True' ORDER BY ip4.added ASC");
while ($row = $result->fetch_array()) {
    $pos = strpos($row[0], "/");
    $ip_arr = [0 => substr($row[0], 0, $pos), 1 => substr($row[0], $pos+1)];
    $start = cidr2ip($ip_arr)[0];
    $end = cidr2ip($ip_arr)[1];
    $count = $count + (ip2long($end) - ip2long($start)) + 1;
}
    unset($result, $row, $pos, $ip_arr, $start, $end, $range);

function cidr2ip($cidr) {
    $start = ip2long($cidr[0]);
    $nm = $cidr[1];
    $num = pow(2, 32 - $nm);
    $end = $start + $num - 1;
    $range = [0 => $cidr[0], 1 => long2ip($end)];
    unset($start, $nm, $num, $end);
    return $range;
}

The secret here is the way in which I create the $ip_arr variable. Rather than using PHP explode, I set the array dimensions manually for each IP address. This is of course a database version of the code and cycles through all IP addresses in the database which meet the search criteria.

1
Ish On

Extension to @jonavon 's answer. If you need flat list of IPs. You can convert his function like below.

function cidrToRange($value) {
    $range = array();
    $split = explode('/', $value);
    if (!empty($split[0]) && is_scalar($split[1]) && filter_var($split[0], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
        $rangeStart = ip2long($split[0]) & ((-1 << (32 - (int)$split[1])));
        $rangeEnd = ip2long($split[0]) + pow(2, (32 - (int)$split[1])) - 1;

        for ($i = $rangeStart; $i <= $rangeEnd; $i++) {
            $range[] = long2ip($i);
        }
        return $range;
    } else {
        return $value;
    }
}
0
Damien On

The utility cidrl will do this:

$ cidrl 73.35.143.32/27
73.35.143.32
73.35.143.33
73.35.143.34
...
73.35.143.63
0
DoomStar On

this returns an array of ips:

function get_list_ip($ip_addr_cidr){
    $ip_arr = explode("/", $ip_addr_cidr);    
    $bin = "";

    for($i=1;$i<=32;$i++) {
        $bin .= $ip_arr[1] >= $i ? '1' : '0';
    }

    $ip_arr[1] = bindec($bin);

    $ip = ip2long($ip_arr[0]);
    $nm = $ip_arr[1];
    $nw = ($ip & $nm);
    $bc = $nw | ~$nm;
    $bc_long = ip2long(long2ip($bc));

    for($zm=1;($nw + $zm)<=($bc_long - 1);$zm++)
    {
        $ret[]=long2ip($nw + $zm);
    }
    return $ret;
}
0
PVi1 On

Fixed version of Kosmonaft script:

function get_list_ip($ip_addr_cidr){

        $ip_arr = explode("/", $ip_addr_cidr);    
        $bin = "";

        for($i=1;$i<=32;$i++) {
            $bin .= $ip_arr[1] >= $i ? '1' : '0';
        }

        $ip_arr[1] = bindec($bin);

        $ip = ip2long($ip_arr[0]);
        $nm = $ip_arr[1];
        $nw = ($ip & $nm);
        $bc = $nw | ~$nm;
        $bc_long = ip2long(long2ip($bc));

        echo "Number of Hosts:    " . ($bc_long - $nw - 1) . "<br/>";
        echo "Host Range:         " . long2ip($nw + 1) . " -> " . long2ip($bc - 1) . "<br/>". "<br/>";

        for($zm=1;($nw + $zm)<=($bc_long - 1);$zm++)
        {
            echo long2ip($nw + $zm).  "<br/>";
        }
    }
0
shengyueming On

I come up with a better idea

$ip_from= long2ip(ip2long($ip)& (-1<<(32-$net_mask)));
$ip_to= long2ip(ip2long($ip)| (~(-1<<(32-$net_mask))));

P.S:$ip is a ipv4 address like 60.12.34.5;$net_mask is a int net mask like 25;

it's very fast because of the bit shift operation