4

I've seen various questions and answers around this site and I'm still having difficulty wrapping my head around this problem (could be because I've got a cold). Regardless, I'm trying to come up with a small web app that will create tables of IP addresses for each of our offices.

Like say if I create a new scope for 10.1.10.0/4 it will create an array (which I can then print to a table) of:

 10.1.10.0 network ID
 10.1.10.1 gateway
 10.1.10.2 usable
 10.1.10.3 broadcast

(not that it would insert the descriptions automatically but that's what we'd be doing).

I'm pretty sure I'm going to be using ip2long/long2ip to store the addresses as integers but still.

greentiger
  • 85
  • 1
  • 2
  • 9
  • `10.1.10.0/4` is 1/16 of the entire IPv4 range (268 million addresses). Those four addresses are `10.1.10.0/28`. –  Apr 12 '13 at 04:13
  • yes instead of 10.1.10.0/4 i meant 10.1.10.0/30. As I said, I had a cold at the time and wasn't thinking very clearly. Though I still need help with coming up with a method that would generate the IP array. Problem: generate an array of IP addresses based on user input of Network ID/CIDR bitmask. so that if the user enters 10.1.10.0/30, it will generate an array of: array[0] 10.1.10.1 array[1] 10.1.10.2 array[2] 10.1.10.3 So it can be stored in a SQLite database. – greentiger Apr 16 '13 at 19:21
  • This is not a specific question, you are giving the program requirements. What have you tried? What isn't working? – Allan Spreys Apr 16 '13 at 21:55

5 Answers5

13

As you've already noted, all IPv4 addresses can be converted to numbers using ip2long(), and converted back using long2ip(). The critical extra bit I'm not sure you've noticed is that sequential IPs correspond with sequential numbers, so you can manipulate these numbers!

Given a CIDR prefix (e.g, $prefix = 30 for your range), you can calculate the number of IPs in that range using a bit shift operator:

$ip_count = 1 << (32 - $prefix);

And then loop through all the IPs in that range using:

$start = ip2long($start_ip);
for ($i = 0; $i < $ip_count; $i++) {
    $ip = long2ip($start + $i);
    // do stuff with $ip...
}
Danny Beckett
  • 20,529
  • 24
  • 107
  • 134
  • OK, that mostly works, however i don't understand the 1 << (32 - $prefix); bit. When I enter 30 as a bitmask it returns values 10.1.10.0 to 10.1.10.29. What is the << doing? – greentiger Apr 18 '13 at 20:41
  • 1
    OK, I figured out how I wanted to do it: $bitmask = 30; $ipaddr = "10.1.10.0"; function buildArray ($ipaddr, $bitmask) { $ipcount = pow(2, (32 - $bitmask)); $start = ip2long($ipaddr); for ($beat = 0; $beat < $ipcount; $beat++) { $iparr[$beat] = long2ip($start + $beat); } return $iparr; } $sample = buildArray($ipaddr, $bitmask); foreach($sample as $value) { print "$value
    \n"; }
    – greentiger Apr 18 '13 at 21:25
  • That's kind of a roundabout way of doing things — you're building an array, then printing it out? You might as well just print the IPs outright. :) –  Apr 18 '13 at 21:55
  • Also, `pow(2, (32 - $bitmask))` calculates the same thing as `1 << (32 - $bitmask)`. –  Apr 18 '13 at 21:56
  • well the printing out of the array was just a quickie way to see if/how it was working. – greentiger Apr 18 '13 at 23:20
  • This is actually not correct. It only goes forward and not backward in the actual IP block range. For instance, this script takes 207.64.1.68/28 starting at 207.64.1.68 and ending at 207.64.1.83, when it should be starting at 207.64.1.64 ending at 207.64.1.79 – tmarois Mar 18 '19 at 19:36
  • 1
    @timothymarois That isn't a valid CIDR range. The prefix must have all of the network bits clear -- the correct way of representing that range would be "207.64.1.64/28". –  Mar 18 '19 at 19:53
11

Here is the actual way to calculate a true IP range based on CIDR:

The top answer is actually not correct. It gives the wrong IP range list.

function ipRange($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(ipRange("207.64.1.68/28"));

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

IP: 207.64.1.68

Should give me all IPs within 207.64.1.64 and 207.64.1.79

The other answers do not subtract, they only go from 207.64.1.68 - 207.64.1.83 (not correct in the IP range block)

You can check it here: https://www.ipaddressguide.com/cidr

tmarois
  • 2,424
  • 2
  • 31
  • 43
  • Code originally posted here https://stackoverflow.com/questions/4931721/getting-list-ips-from-cidr-notation-in-php – Wadih M. Sep 15 '22 at 20:16
4

I am using the following function to give me the network, 1st usable, last usable and the broadcast address along with all hosts:

function ipv4Breakout ($ip_address, $ip_nmask) {
    $hosts = array();
    //convert ip addresses to long form
    $ip_address_long = ip2long($ip_address);
    $ip_nmask_long = ip2long($ip_nmask);

    //caculate network address
    $ip_net = $ip_address_long & $ip_nmask_long;

    //caculate first usable address
    $ip_host_first = ((~$ip_nmask_long) & $ip_address_long);
    $ip_first = ($ip_address_long ^ $ip_host_first) + 1;

    //caculate last usable address
    $ip_broadcast_invert = ~$ip_nmask_long;
    $ip_last = ($ip_address_long | $ip_broadcast_invert) - 1;

    //caculate broadcast address
    $ip_broadcast = $ip_address_long | $ip_broadcast_invert;

    foreach (range($ip_first, $ip_last) as $ip) {
            array_push($hosts, $ip);
    }

    $block_info = array(array("network" => "$ip_net"),
            array("first_host" => "$ip_first"),
            array("last_host" => "$ip_last"),
            array("broadcast" => "$ip_broadcast"),
            $hosts);

    return $block_info;
}

I also noticed that you are asking to calculate based on a CIDR notation. Here is a function that I am using to convert from CIDR to dotted decimal:

function v4CIDRtoMask($cidr) {
    $cidr = explode('/', $cidr);
    return array($cidr[0], long2ip(-1 << (32 - (int)$cidr[1])));
}

I deal mainly dotted decimal and not with CIDR notation. The ipv4Breakout function returns a multidimensional array with all the information that you need via long format. You will need to use long2ip() if you want the actual dotted decimal IP Address. The function requires IP Address and a subnet mask via dotted decimal format.

Hope this helps you or anyone else out.

Rethmann
  • 41
  • 2
  • I tried out this code using resp "123.234.123.234" as IP and "255.255.255.255" as netmask. The code takes a long time to execute and then fails on "Allowed memory size exhausted". Is there a bug in this code, or is it simply very inefficient? – vrijdenker Oct 06 '17 at 09:55
  • Also: with netmask 255.255.255.255, I would expect both ip_first and ip_last to be the same, but they are not. Is that correct? Do I misunderstand it? – vrijdenker Oct 06 '17 at 09:57
2

My version to help you use variables.

<?php
$ip_address = "192.168.0.2";
$ip_nmask = "255.255.255.0";
ipv4Breakout($ip_address, $ip_nmask);

function ipv4Breakout ($ip_address, $ip_nmask) {
    //convert ip addresses to long form
    $ip_address_long = ip2long($ip_address);
    $ip_nmask_long = ip2long($ip_nmask);
    //caculate network address
    $ip_net = $ip_address_long & $ip_nmask_long;
    //caculate first usable address
    $ip_host_first = ((~$ip_nmask_long) & $ip_address_long);
    $ip_first = ($ip_address_long ^ $ip_host_first) + 1;
    //caculate last usable address
    $ip_broadcast_invert = ~$ip_nmask_long;
    $ip_last = ($ip_address_long | $ip_broadcast_invert) - 1;
    //caculate broadcast address
    $ip_broadcast = $ip_address_long | $ip_broadcast_invert;

    //Output
    $ip_net_short = long2ip($ip_net);
    $ip_first_short = long2ip($ip_first);
    $ip_last_short = long2ip($ip_last);
    $ip_broadcast_short = long2ip($ip_broadcast);
    echo "Network - " . $ip_net_short . "<br>";
    echo "First usable - " . $ip_first_short . "<br>";
    echo "Last usable - " . $ip_last_short . "<br>";
    echo "Broadcast - " . $ip_broadcast_short . "<br>";
}
Elangovan
  • 3,469
  • 4
  • 31
  • 38
bluenapalm
  • 47
  • 2
0

I created this php function in order to get the subnet splits of a network/range. It works with ipv4 or ipv6.
If php<5.4 then use this hex2bin function https://github.com/dompdf/dompdf/issues/1692

function divide_ip_range($cidr="2001:db8:abc:12ff::/54",$mindivs=2){ // input range 192.168.4.0/24 and returns array with the 1st range and 2nd range [0] => 192.168.4.0/25 , [1] => 192.168.4.127/25
  $outarr=array();
  list($ipaddr,$range) = explode('/', $cidr);
  for($rngsplit=1;pow(2,$rngsplit)<$mindivs;$rngsplit++){} // find to how many networks to divide
  $divs=pow(2,$rngsplit);    
  $divcidr=(int)$range+$rngsplit;
  if(preg_match("/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/",$ipaddr)){ // IPv4
    $ip_address_long = ip2long($ipaddr);
    $ip_nmask_long=(-1 << (32 - $range));
    $ip_net = $ip_address_long & $ip_nmask_long;
    $ip_broadcast_invert = ~$ip_nmask_long;
    $ip_last = ($ip_address_long | $ip_broadcast_invert) - 1;
    $ip_broadcast = $ip_address_long | $ip_broadcast_invert;
    $numofhosts=pow(2,32-$range)-2;
    for ($i=0;$i<$divs;$i++){
      $outarr[]=long2ip($ip_net+$i*ceil($numofhosts/$divs)+($i*ceil($numofhosts/$divs)%2) )."/$divcidr";
    }
    //echo "Net:$ipaddr/$range\nFirst:".long2ip($ip_net)."\nLast: ".long2ip($ip_last)."\nNumOfHosts:$numofhosts \n";
  } else if (preg_match("/^[0-9a-f:]+$/",$ipaddr)) { // IPv6 section 
    $ip_addr_bin = inet_pton($ipaddr);
    $ip_addr_hex = bin2hex($ip_addr_bin);
    $flexbits = 128 - $range; // Calculate the number of 'flexible' bits for first net
    $pos = 31; $addr_hex_first = $ip_addr_hex; $addr_hex_last = $ip_addr_hex;
    while ($flexbits > 0) {
      $orig_val = hexdec(substr($ip_addr_hex, $pos, 1)); // dec value of pos char. ex. f=15
      $mask = 0xf << (min(4,$flexbits)); // calculate the subnet mask. min() prevents comparison to be negative 
      $new_val_first = $orig_val & $mask;
      $addr_hex_first = substr_replace($addr_hex_first, dechex($new_val_first) , $pos, 1); // Put hex character in pos
      $segmask=(pow(2,min(4,$flexbits))-1); // Last address: OR it with (2^flexbits)-1, with flexbits limited to 4 at a time
      $new_val_last = $orig_val | $segmask;
      $addr_hex_last = substr_replace($addr_hex_last, dechex($new_val_last) , $pos, 1);
      $pos--; $flexbits -= 4; // Next nibble
    }
    $partpos=(4*floor($pos/4)); // The 4 digits that vary by the subnetting
    $partfirst=substr($addr_hex_first,$partpos,4);
    $partlast=substr($addr_hex_last,$partpos,4);
    $numofhosts=(hexdec($partlast)+1-hexdec($partfirst));
    for ($i=0;$i<$divs;$i++){
      $partdiv=dechex(hexdec($partfirst)+$i*$numofhosts/$divs);
      $addr_hex_div=substr_replace($addr_hex_first, $partdiv , $partpos, 4);
      $outarr[]=inet_ntop(hex2bin($addr_hex_div))."/$divcidr";
    }  
    //echo "Net:$ipaddr/$range\nFirst:".inet_ntop(hex2bin($addr_hex_first))."\nLast:".inet_ntop(hex2bin($addr_hex_last))."\nNumOfHosts:$numofhosts\nDivide at partpos:$partpos ($partlast+1-$partfirst)/$divs=$partdiv\n";
  }
  return $outarr;
}
SteinAir
  • 96
  • 3