Solved

How do I summarise CIDR addresses in PHP?

Posted on 2007-12-05
7
1,653 Views
Last Modified: 2008-02-01
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

0
Comment
Question by:kebabs
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 4
  • 3
7 Comments
 
LVL 2

Expert Comment

by:Waschman
ID: 20433089
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
 
LVL 8

Author Comment

by:kebabs
ID: 20433610
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
 
LVL 2

Expert Comment

by:Waschman
ID: 20434384
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
Industry Leaders: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
LVL 8

Author Comment

by:kebabs
ID: 20435713
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
 
LVL 8

Author Comment

by:kebabs
ID: 20435717
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
 
LVL 2

Expert Comment

by:Waschman
ID: 20436269
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
 
LVL 2

Accepted Solution

by:
Waschman earned 500 total points
ID: 20475225
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

Featured Post

Independent Software Vendors: We Want Your Opinion

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Things That Drive Us Nuts Have you noticed the use of the reCaptcha feature at EE and other web sites?  It wants you to read and retype something that looks like this. Insanity!  It's not EE's fault - that's just the way reCaptcha works.  But it i…
These days socially coordinated efforts have turned into a critical requirement for enterprises.
The viewer will learn how to dynamically set the form action using jQuery.
The viewer will learn how to create and use a small PHP class to apply a watermark to an image. This video shows the viewer the setup for the PHP watermark as well as important coding language. Continue to Part 2 to learn the core code used in creat…

691 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question