PHP5 calculate IPv6 range from cidr prefix?
First of all: IPv6 doesn't have network and broadcast addresses. You can use all addresses in a prefix. Second: On a LAN the prefix length is always (well, 99.x% of the time) a /64. Routing a /68 would break IPv6 features like stateless auto configuration.
Below is a verbose implementation of an IPv6 prefix calculator:
<?php
/*
* This is definitely not the fastest way to do it!
*/
// An example prefix
$prefix = '2001:db8:abc:1400::/54';
// Split in address and prefix length
list($firstaddrstr, $prefixlen) = explode('/', $prefix);
// Parse the address into a binary string
$firstaddrbin = inet_pton($firstaddrstr);
// Convert the binary string to a string with hexadecimal characters
# unpack() can be replaced with bin2hex()
# unpack() is used for symmetry with pack() below
$firstaddrhex = reset(unpack('H*', $firstaddrbin));
// Overwriting first address string to make sure notation is optimal
$firstaddrstr = inet_ntop($firstaddrbin);
// Calculate the number of 'flexible' bits
$flexbits = 128 - $prefixlen;
// Build the hexadecimal string of the last address
$lastaddrhex = $firstaddrhex;
// We start at the end of the string (which is always 32 characters long)
$pos = 31;
while ($flexbits > 0) {
// Get the character at this position
$orig = substr($lastaddrhex, $pos, 1);
// Convert it to an integer
$origval = hexdec($orig);
// OR it with (2^flexbits)-1, with flexbits limited to 4 at a time
$newval = $origval | (pow(2, min(4, $flexbits)) - 1);
// Convert it back to a hexadecimal character
$new = dechex($newval);
// And put that character back in the string
$lastaddrhex = substr_replace($lastaddrhex, $new, $pos, 1);
// We processed one nibble, move to previous position
$flexbits -= 4;
$pos -= 1;
}
// Convert the hexadecimal string to a binary string
# Using pack() here
# Newer PHP version can use hex2bin()
$lastaddrbin = pack('H*', $lastaddrhex);
// And create an IPv6 address from the binary string
$lastaddrstr = inet_ntop($lastaddrbin);
// Report to user
echo "Prefix: $prefix\n";
echo "First: $firstaddrstr\n";
echo "Last: $lastaddrstr\n";
?>
It should output:
Prefix: 2001:db8:abc:1400::/54
First: 2001:db8:abc:1400::
Last: 2001:db8:abc:17ff:ffff:ffff:ffff:ffff
For those who stumble upon this question, you can do this more effectively using the dtr_pton
and dtr_ntop
functions and dTRIP
class found on GitHub.
We also have noticed a lack of focus and tools with IPv6 in PHP, and put together this article, http://www.highonphp.com/5-tips-for-working-with-ipv6-in-php, which may be of help to others.
Function Source
This converts and IP to a binary representation:
/**
* dtr_pton
*
* Converts a printable IP into an unpacked binary string
*
* @author Mike Mackintosh - [email protected]
* @param string $ip
* @return string $bin
*/
function dtr_pton( $ip ){
if(filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)){
return current( unpack( "A4", inet_pton( $ip ) ) );
}
elseif(filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)){
return current( unpack( "A16", inet_pton( $ip ) ) );
}
throw new \Exception("Please supply a valid IPv4 or IPv6 address");
return false;
}
This converts a binary representation to printable IP:
/**
* dtr_ntop
*
* Converts an unpacked binary string into a printable IP
*
* @author Mike Mackintosh - [email protected]
* @param string $str
* @return string $ip
*/
function dtr_ntop( $str ){
if( strlen( $str ) == 16 OR strlen( $str ) == 4 ){
return inet_ntop( pack( "A".strlen( $str ) , $str ) );
}
throw new \Exception( "Please provide a 4 or 16 byte string" );
return false;
}
Examples
Using the dtr_pton
function you can:
$ip = dtr_pton("fe80:1:2:3:a:bad:1dea:dad");
$mask = dtr_pton("ffff:ffff:ffff:ffff:ffff:fff0::");
Get your Network and Broadcast:
var_dump( dtr_ntop( $ip & $mask ) );
var_dump( dtr_ntop( $ip | ~ $mask ) );
And your output would be:
string(18) "fe80:1:2:3:a:ba0::"
string(26) "fe80:1:2:3:a:baf:ffff:ffff"