How do I summarise CIDR addresses in PHP?

I have a list of CIDR addresses that can be summarised/aggregated and I wish to do this in PHP but cannot find a PEAR package or class through the likes of http://phpclasses.org to do this task.

There is a Perl extension Net::CIDR::Lite and a Ruby class but I'm having a hard time trying to convert this to PHP nor do I want to spend much time on that.

Here is an excerpt of a possible output.
58.6.0.0/15
58.65.248.0/21
58.84.64.0/18
58.84.128.0/18
58.84.192.0/19
58.84.240.0/20
58.87.0.0/20
58.96.0.0/16
58.97.128.0/17
58.104.0.0/13
58.145.128.0/19
58.160.0.0/12
58.178.0.0/15
58.181.64.0/19
58.181.112.0/20
59.86.160.0/19
59.100.0.0/15
59.102.0.0/16
59.151.128.0/18
59.152.224.0/19
59.154.0.0/18
59.154.64.0/19
59.154.96.0/23
59.154.98.0/24
59.154.99.0/25
59.154.99.128/27
59.154.99.160/28
59.154.99.176/29
59.154.99.184/30
59.154.99.192/26
59.154.100.0/22
59.154.104.0/21
59.154.112.0/20
59.154.128.0/17
59.167.0.0/16
59.191.192.0/19
59.191.224.0/20

Open in new window

LVL 8
kebabsAsked:
Who is Participating?
 
WaschmanConnect With a Mentor Commented:
Hello again!
I got tired of studying all day long so I sat down tonight a few hours and tried to complete the code. And yeehaa, it's working pretty good right now. Atleast during my few tests. The IP-addresses that I've tried are commented almost at the bottom of the script. Just uncomment a block you want to try. Something important to note is that the code is not optimized and there are some missing error-checking-rutines (valid IP etc). Maybe there's an easier way of coding this, but I've done it quite fast and the code works (for the moment!) so I don't care about optimizing the code at the moment.

You said that I should add contact information if I did finish the code so you could give me a better award. That do you mean with this - money? If you mean that I have a paypal account you could send to (if you know what it is). I'll give you my contact information if you give a reply or if you send me an e-mail to waschman at gmail dot com.

Anyway, here's the code that works:
<?php
/**
 * Filename: cidr-summarize.php
 * Owner: Waschman, waschman at gmail dot com, waschman.blogspot.com (swedish)
 */
 
 
/**
 * Get single IP-address class
 *
 * @param string $ip
 */
function get_ip_class($ip)
{
        $class = false;
        $octet = explode('.', $ip);
        if ($octet[0] <= 126) $class = 'A';
        elseif ($octet[0] == 127) $class = 'Loopback';
        elseif ($octet[0] <= 191) $class = 'B';
        elseif ($octet[0] <= 223) $class = 'C';
        elseif ($octet[0] <= 239) $class = 'Multicast';
        else $class = 'Reserved';
        return $class;
}
 
 
/**
 * Decimal to network binary, preceeded with 0
 *
 * @param int $dec
 * @return string
 */
function dec2netbin($dec)
{
	$ip = str_pad((string)(decbin($dec)), 8, "0", STR_PAD_LEFT);
	return $ip;
}
 
 
/**
 * This returns 1 if both variables are 0 and 0 or 1 and 1.
 * Regular binary AND returns 1 if both only are 1 and 1.
 * 
 * OBS! Don't work on numbers, only a single 0 and 1!
 *
 * @param bit $a
 * @param bit $b
 * @return string (0 or 1)
 */
function special_binary_and($a, $b)
{
	if ($a == $b) return '1';
	else return '0';
}
 
 
/**
 * Summarizes IP-addresses into a single CIDR address with mask-suffix.
 *
 * IMPORTANT NOTE: This function needs to be optimized and cleaned. Correct 
 * error-checking should also be implemented. Code not for use in critical 
 * computer environments. Also note that all functions included in this package
 * is required, except for the get_ip_class() function.
 * 
 * @author Waschman, waschman at gmail dot com, waschman.blogspot.com (swedish)
 * @copyright Free for everyone to use, but only if you keep author and copyright information intact.
 * @version 0.8.0
 * @param array $ip
 * @param bool $debugon
 * @return array
 */
function cidrsummarize($ips, $debugon=false)
{
	//
	// Code should be cleaned and optimized.
	//
	
    $count = count($ips);
    $octet_diff = 0;
    // Save all octets into an array
    for ($pos=0; $pos<4; $pos++) {
            for ($i=0; $i<$count; $i++) {
                    $octets[$i] = explode('.', $ips[$i], 4);
                    // Save first octet so we can check which octet differs from
                    // the others. Crucial when we want to know where to begin 
                    // checking of binary digits.
                    if ($i==0)
                    {
                    	$first_ip = $octets[$i][$pos];
                    	echo $debugon?'<p>$firstip:'. $first_ip .'</p>':'';
                    	continue;
                    }
                    // Find different octets
                    echo $debugon?'<p>IS '. $first_ip .' == '. 
                    	$octets[$i][$pos] .'? ':'';
                    if ($first_ip != $octets[$i][$pos]) {
                    	echo $debugon?'no, that means we found an octet that is
                    		different.</p>':'';
                        echo $debugon?'Debug: Position of octet that 
                        	differs is '. $i .':'. $pos:'';
                        $octet_diff = $pos;// Save position that is different
                        break 2;// Exit 2 for-loops
                    }
                    echo $debugon?'yes</p>':'';
            }
    }
    
    
    //
    // Set the number of bits so far to use as mask.
    // This depends on which octet that differ.
    //
    $mask_bits = 0;
    if ($octet_diff < 1) $mask_bits = 0;
    elseif ($octet_diff < 2) $mask_bits = 8;
    elseif ($octet_diff < 3) $mask_bits = 16;
    else $mask_bits = 24;
    
    //
    // Starting main-loop for checking diffing bits.
    //
    $count = count($octets); 
    for ($i=$octet_diff; $i<4; $i++) {
    	if ($i==$octet_diff)
    	{
    		$first_octet = $octets[0][$i];
    	}
    	
    	for ($bit_pos=0; $bit_pos<8; $bit_pos++) {
    	
    		echo $debugon?'<h2>Debug: '. $bit_pos .'</h2>':'';
        	for ($j=0; $j<$count; $j++) {
        		if ($j==0)
        		{
        			continue;
        		}
        		else 
        		{
        			$next_octet = $octets[$j][$i];
        		}
        		
        		echo $debugon?'<p>Debug: First octet='. $first_octet 
        			.', next octet='. $next_octet .'</p>'."\n":'';
        		
        		// ANDing bits!
        		$a = dec2netbin($first_octet);
        		$b = dec2netbin($next_octet);
        		$anding = special_binary_and($a[$bit_pos], $b[$bit_pos]);
        		echo $debugon?'<p>Debug: ('. $a .')'. $a[$bit_pos] .' : ('. 
        			$b .')'. $b[$bit_pos] .'</p>':'';
        		
        		// If we find a bit that doesn't match
        		if ($anding == 0)
        		{
        			$add_mask_bits = $bit_pos;// create the mask
        			$cidr_octet = str_pad('', 8, '0', STR_PAD_LEFT);
        			// create the cidr ip octet (in binary)
        			if ($bit_pos != 0)
        			{
        				$cidr_octet[$bit_pos-1] = 1;
        			}
        			else 
        			{
        				echo $debugon?'Debug: $bit_pos='. $bit_pos .'<br>':'';
        				$cidr_octet[$bit_pos] = 0;
        			}
        			echo $debugon?'<h2>Debug: Different octet, new number: '. 
        				$cidr_octet .'</h2>':'';
        			$cidr_octet = bindec($cidr_octet);// octet in decimal
        			break 3;// exit 3 for-loops
        		}
        	}
        	
    	}
    }
    
    //
    // Create the real cidr ip address from the information gathered
    //
    $cidr_ip = '';
    for ($octet_idx=0; $octet_idx<4; $octet_idx++) {
    	if ($octet_idx==$octet_diff)
    	{
    		$cidr_ip .= $cidr_octet .'.';
    	}
    	elseif ($octet_idx > $octet_diff)
    	{
    		$cidr_ip .= 0 .'.';
    	}
    	else 
    	{
    		$cidr_ip .= $octets[0][$octet_idx] .'.';
    	}
    }
    
    // Cut off trailing dot
    $cidr_ip = substr($cidr_ip, 0, -1);
    
    
    //
    // Create the /bits mask
    //
    $mask_bits += $add_mask_bits;
    
    
    //
    // Create the binary mask
    //
    $mask_binary = '';
    for ($i=1; $i<=32; $i++) {
    	if ($i <= $mask_bits)
    	{
        	if ($i % 8 == 0)
        		$mask_binary .= '1.';//add dot
        	else
        		$mask_binary .= '1';
    	}
    	else 
    	{
    		if ($i % 8 == 0)
        		$mask_binary .= '0.';//add dot
        	else
        		$mask_binary .= '0';
    	}
    }
    
    // Cut off trailing dot
    $mask_binary = substr($mask_binary, 0, -1);
    
    
    //
    // Create the dotted decimal mask
    //
    $mask_decimal_parts = explode('.', $mask_binary, 4);
    $mask_dotted_decimal = bindec($mask_decimal_parts[0]) .'.';
    $mask_dotted_decimal .= bindec($mask_decimal_parts[1]) .'.';
    $mask_dotted_decimal .= bindec($mask_decimal_parts[2]) .'.';
    $mask_dotted_decimal .= bindec($mask_decimal_parts[3]);
    
    
    echo $debugon?'<p>Debug: Mask bits is: /'. $mask_bits  .'<br> 
    	IP-address is: '. $cidr_ip .'<br>
    	Subnetmask is: '. $mask_dotted_decimal .'<br>
    	Subnetmask in binary is: '. $mask_binary .'</p>':'';
	
    $return_array['cidr_ip'] = $cidr_ip;
    $return_array['mask_bits'] = $mask_bits;
    $return_array['mask_dotted_decimal'] = $mask_dotted_decimal;
    $return_array['mask_binary'] = $mask_binary;
    
    return $return_array;
}
 
 
// *** Remove the comments from one of the blocks to test the summarization. ***
 
 
// My example :: Should summarize into 192.168.16.0/20
/*$ip[] = '192.168.1.0';
$ip[] = '192.168.2.0';
$ip[] = '192.168.3.0';
$ip[] = '192.168.4.0';
$ip[] = '192.168.5.0';
$ip[] = '192.168.6.0';
$ip[] = '192.168.7.0';
$ip[] = '192.168.8.0';
$ip[] = '192.168.9.0';
$ip[] = '192.168.10.0';*/
 
// My example :: Should summarize into 128.0.0.0/1
/*$ip[] = '191.168.4.0';
$ip[] = '192.168.5.0';
$ip[] = '193.168.6.0';
$ip[] = '194.168.7.0';*/
 
// Example addresses from your submitted examples
/*$ip[] = '82.205.190.0';
$ip[] = '82.205.192.0';
$ip[] = '82.205.202.0';
$ip[] = '82.205.204.0';
$ip[] = '82.205.246.0';*/
 
$ip[] = '203.88.66.64';
$ip[] = '203.88.66.160';
$ip[] = '203.88.82.240';
$ip[] = '203.88.88.24';
$ip[] = '203.88.88.32';
$ip[] = '203.88.88.40';
$ip[] = '203.88.88.160';
$ip[] = '203.88.88.224';
 
/*$ip[] = '217.10.167.0';
$ip[] = '217.195.144.24';
$ip[] = '217.195.144.32';*/
 
 
 
 
 
// Return an array with information about the summarization.
// You can use this in the URL if you want debugging: [this-file].php?debug=1
$use_debugging = (isset($_GET['debug'])) ? $_GET['debug'] : false;
$cidr_ip = cidrsummarize($ip, $use_debugging);
echo '<hr><p>Summarized IP-address: '. $cidr_ip['cidr_ip'] .'/'. $cidr_ip['mask_bits'] .'</p>';
 
echo '<pre>';
print_r($cidr_ip);
echo '</pre>';
?>

Open in new window

0
 
WaschmanCommented:
You'll need to use the binary operators in PHP (| & << >> etc).
Then you can use the convert-to-binary functions to convert between int and bin.

I assume you know how to calculate summaries with CIDR addressing.
// &	AND
// |	OR
// >>	right-bit-shift
// <<	left-bit-shift
// ^	XOR
$a = 8;
$b = 5;
$c = $a | $b;
echo $c .'<br>';// Prints 13
 
$a = 46;
$b = 248;
$c = $a & $b;
echo $c .'<br>';// Prints 40
 
$a = 10;
$b = 5;
$c = $a ^ $b;
echo $c .'<br>';// Prints 15
 
$a = 128;//10000000
$b = 1;
$c = $a >> $b;//0100000
echo $c .'<br>';// Prints 64
 
$a = 6;//110
$b = 2;
$c = $a << $b;//11000
echo $c .'<br>';// Prints 24

Open in new window

0
 
kebabsAuthor Commented:
Bitwise operators give me too much of a head ache, possibly the worst aspect of writing code. Otherwise, I could simply transcode the Perl/Ruby variants. I was hoping for a ready solution.
0
Free Tool: ZipGrep

ZipGrep is a utility that can list and search zip (.war, .ear, .jar, etc) archives for text patterns, without the need to extract the archive's contents.

One of a set of tools we're offering as a way to say thank you for being a part of the community.

 
WaschmanCommented:
Sorry for that! I searched Google and it didn't come up with anything useful. I want to make a real code that works but this weekend I don't have the time. Maybe tonight when I come home. I'm a nightowl! One question though... are the IP-addresses you submitted already summarized? (I think they are) If so, can you please send some example IP-addresses that should be summarized.

For example: Any IPs that summarize into 58.84.240.0/20.
0
 
kebabsAuthor Commented:
The example was not a good one... below is one that summarises better. If you do take the time to write one, make sure to leave a point of contact as I doubt some points here are enough reward :)
# Summarised (9 CIDR addresses)
58.147.128.0/19
117.55.192.0/20
121.100.48.0/21
125.213.192.0/19
202.56.176.0/20
202.86.16.0/20
203.215.32.0/20
210.80.0.0/19
210.80.32.0/19
 
# Unsummarised (72 CIDR addresses)
58.147.128.0/19
62.142.210.32/27
63.243.149.0/27
63.243.150.8/29
63.243.150.16/28
64.86.63.64/27
80.247.139.0/24
82.205.190.0/23
82.205.192.0/21
82.205.202.0/23
82.205.204.0/22
82.205.246.0/25
84.11.26.128/26
84.11.33.0/24
84.11.79.0/24
87.249.84.0/24
117.55.192.0/20
117.104.224.0/21
121.100.48.0/21
121.127.32.0/19
125.213.192.0/19
202.56.176.0/20
202.86.16.0/20
202.133.9.240/28
202.133.13.8/29
202.133.13.16/28
202.133.13.40/29
202.133.13.56/29
202.133.13.96/28
202.174.133.8/29
202.174.133.32/28
202.174.133.144/29
202.174.134.0/29
202.174.144.0/29
202.174.144.216/29
202.174.148.48/29
202.174.148.128/29
202.174.153.184/29
202.174.154.0/29
202.174.154.24/29
202.174.154.96/28
202.174.154.128/29
202.174.154.160/29
202.174.154.180/30
202.174.154.184/30
202.174.154.212/30
202.174.155.128/29
202.174.155.144/29
202.174.155.216/29
202.174.155.240/29
202.174.156.64/26
202.174.156.192/28
202.174.156.208/29
202.174.156.248/29
202.174.157.192/27
202.174.157.232/29
203.88.66.64/29
203.88.66.160/29
203.88.82.240/29
203.88.88.24/29
203.88.88.32/29
203.88.88.40/30
203.88.88.160/29
203.88.88.224/29
203.208.205.192/26
203.215.32.0/20
208.35.53.176/28
210.5.226.0/24
212.116.232.208/28
217.10.167.0/24
217.195.144.24/29
217.195.144.32/30

Open in new window

0
 
kebabsAuthor Commented:
BTW, this was data from 2 different sources covering Afghanistan's IP ranges (the country was only for example's sake). I think the unsummarised list is an older/newer list as there seems to be some ranges that are not represented in the summarised one.
0
 
WaschmanCommented:
Hmm, it was more difficult than I thought it should be. I'm too tired I have to much to do in school so I don't have the time. I have a problem with my code-logic, but I'm posting my code so far and some pseudo-code for you to try. I need to point out that I'm studying for the CCNA exam and so my CIDR knowledge is a bit bad, but I think I know how to calculate it (taking common bits and return network_address/bits). See pseudo for more details.

I am sorry for this but I hope you get a solution soon. You can e-mail me on waschman at gmail dot com when you find a solution. Good luck!
/**
 * Get single IP-address class
 *
 * @param string $ip
 */
function get_ip_class($ip)
{
	$class = false;
	$octet = explode('.', $ip);
	if ($octet[0] <= 126) $class = 'A';
	elseif ($octet[0] == 127) $class = 'Loopback';
	elseif ($octet[0] <= 191) $class = 'B';
	elseif ($octet[0] <= 223) $class = 'C';
	elseif ($octet[0] <= 239) $class = 'Multicast';
	else $class = 'Reserved';
	return $class;
}
 
 
/**
 * Enter description here...
 *
 * @param array $ip
 */
function summarize($ips)
{
	$count = count($ips);
	$octet_diff = 0;
	// Save all octets into an array
	for ($pos=0; $pos<4; $pos++) {
		for ($i=0; $i<$count; $i++) {
			$octets[$i] = explode('.', $ips[$i], 4);
			// Save first octet so we can check which octet differs from
			// the others. Crucial when we want to know where to begin checking
			// of binary digits.
			if ($i==0) $first = $octets[$i][$pos];
			// Find different octets
			if ($first != $octets[$i][$pos]) {
				$debug = $i .':'. $pos;//save which IP and octet
				$octet_diff = $pos;
				break;
			}
		}
	}
	
	echo 'Debug: Position of octet that differs is '. $debug .'<br>';
	
	// Logic problem here -- too tired and too much in to do in school instead
	
	// Some kind of main-loop.
	// Check the diffing octet in IP x and x+1.
	// Convert them both to binary (or leave as int).
	// Loop through binary positions, starting from the left.
	// Is ANDing y bit of octet in IP x y bit of octet in IP x+1 equal to 1?
	// If 1 goto next loop.
	// Now compare y bit of octet in IP x+1 and x+2.
	// Is ANDing equal to 1?
	// And so on...
	//
	// Loop through y bit of all IP:s passed to summarize().
	// If every ANDing did get 1 move on to y+1 bit.
	// And repeat above until you encounter an ANDing equal to 0.
	// If 0 then we have all the bits needed to summarize the IP-addresses.
	//   We only have to keep track of the number of bits tested from left
	//   to right to have the number of bits used for the subnetmask.
}
 
 
 
// Should summarize into 192.168.16.0/20
$ip[] = '192.168.1.0';
$ip[] = '192.168.2.0';
$ip[] = '192.168.3.0';
$ip[] = '192.168.4.0';
$ip[] = '192.168.5.0';
$ip[] = '192.168.6.0';
$ip[] = '192.168.7.0';
$ip[] = '192.168.8.0';
$ip[] = '192.168.9.0';
$ip[] = '192.168.10.0';
 
$cidr_ip = summarize($ip);
echo '<p>'. $cidr_ip .'</p>';

Open in new window

0
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

All Courses

From novice to tech pro — start learning today.