05 februari, 2008

PHP/Nätverk: CIDR IP Summarize

För ungefär en månad sedan hjälpe jag någon på ett forum med ett PHP-skript som kan summera IP-adresser med hjälp av CIDR. Det är något som används i många routrar för att minska routing tabellerna och därmed minska belastningen på resurserna i routern vid eventuella beräkningar för olika nätverk. Kort sammanfattat kan man säga att flera nätverk representeras av en enda IP-adress.

Jag lyckades koda ihop en lösning i PHP ganska snabbt men koden har inte optimerats för bästa prestanda och några felhanteringsrutiner finns inte. I framtiden funderar jag på att försöka göra om skriptet till ett lite smidigare. Personen jag hjälpte ville bara ha en lösning och det fick han/eller hon som utlovat.

Kommentarerna och koden är på engelska för att öka tillgängligheten. Det är även enklare att porta koden om någon icke-svensk vill använda den. Jag har försökt kommentera det mesta men märker att det fattas på några ställen; vilka jag tycker är oviktiga för tillfället.

Uppdatering 9/10-08
Koden har fixats till och ger nu ett korrekt resultat. Efter grundlig felsökning kom jag fram till att man fick fel resultat ibland. Jag hade helt enkelt tänkt fel. Men nu fungerar koden som den ska. Nackdelen är dock att den fortfarande är rörig.

<?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';
//return $a & $b;
}


/**
* 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);
if ($debugon) print_r($octets);
for ($i=$octet_diff; $i<4; $i++) {
if ($i==$octet_diff)
{
$first_octet = $octets[0][$i];
}

// Go through the bits one by one in the diffing octet
$bit_index_all_ones = 0;
$final_octet = '';
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];
}
$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 = $a[$bit_pos] & $b[$bit_pos];

// When equal-bit becomes 0 then there's no more bits that are the same
$equal_bit = special_binary_and($a[$bit_pos], $b[$bit_pos]);

echo "BIT: $bit_pos\tAND: $anding\t";
echo "F: $first_octet\tN: $next_octet\n";

// Create the new octet
if ($equal_bit == 1 && $j == $count-1)
{
$final_octet .= $anding;
}

// No more matches... now we have the bits for the mask
if ($equal_bit == 0)
{
$add_mask_bits = $bit_pos;// create the mask
$final_octet = str_pad($final_octet, 8, '0', STR_PAD_RIGHT);
$cidr_octet = bindec($final_octet);
echo "<pre>\n\n\tFinal octet = $final_octet\n\tCIDR octet = $cidr_octet\n";
echo "\tBitmask: /". ($mask_bits + $add_mask_bits) ."\n\n</pre>";
break 3;// exit 3 for-loops
}

}// EndFor $j

}// EndFor $bit_pos
}

//
// 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.0.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 - 82.205.128.0/17
/*$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';*/

// Example 203.88.64.0/19
/*$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';*/

// 217.0.0.0/8
/*$ip[] = '217.10.167.0';
$ip[] = '217.195.144.24';
$ip[] = '217.195.144.32';*/

// Example from CCNA3 v311
// Module 1.1.5 (Route Summarization #2)
//
// => 200.199.48.0/20
// Reality: 200.199.48.0/20
//
$ip[] = '200.199.48.0';
$ip[] = '200.199.52.0';
$ip[] = '200.199.56.0';




// 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);
$buf = '';
$buf .= '<hr><p>Summarized IP-address: '. $cidr_ip['cidr_ip'] .'/'. $cidr_ip['mask_bits'] .'</p>';

$buf .= '<pre>';
foreach ($cidr_ip as $value) {
$buf .= $value .'<br/>'."\n";
}
$buf .= '</pre>';
file_put_contents('results.html', $buf);
?>

Mycke nöje och jag hoppas att koden är användbar för i alla fall några av er.

Inga kommentarer:

Välkomna till bloggen

Välkomna ska ni vara kära besökare. Jag skriver om IT för att det är kul och för att jag vill dela med mig av information och kunskap. Jag försöker hålla bloggen så kategoriserad som möjligt för att ni enklare ska hitta intressanta länkar och artiklar.