I am able to do this with IPv4 using code snippets from various online sources. I was wondering if there was a way to do it with IPv6.
Basically I just need a form that I can enter an IPv6 address and prefix (ex: f080:42d2:581a::0/68) and it calculates the network address, first useable address, last useable address, and broadcast address. Then just prints to screen. Not looking to store it in a database or anything yet.
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
This is a fix to the accepted answer, which incorrectly assumes the "first address" should be identical to the inputted string. Rather, it needs to have its value modified via an AND operator against its mask.
To demonstrate the problem, consider this example input: 2001:db8:abc:1403::/54
Expected result:
First: 2001:db8:abc:1400::
Actual result:
First: 2001:db8:abc:1403::
The relevant math to calculate the mask for a given 4-bit sequence is:
// Calculate the subnet mask. min() prevents the comparison from being negative
$mask = 0xf << (min(4, $flexbits));
// AND the original against its mask
$newval = $origval & $mask;
Full code
<?php
/*
* This is definitely not the fastest way to do it!
*/
// An example prefix
$prefix = '2001:db8:abc:1403::/54';
// Split in address and prefix length
list($addr_given_str, $prefixlen) = explode('/', $prefix);
// Parse the address into a binary string
$addr_given_bin = inet_pton($addr_given_str);
// Convert the binary string to a string with hexadecimal characters
$addr_given_hex = bin2hex($addr_given_bin);
// Overwriting first address string to make sure notation is optimal
$addr_given_str = inet_ntop($addr_given_bin);
// Calculate the number of 'flexible' bits
$flexbits = 128 - $prefixlen;
// Build the hexadecimal strings of the first and last addresses
$addr_hex_first = $addr_given_hex;
$addr_hex_last = $addr_given_hex;
// We start at the end of the string (which is always 32 characters long)
$pos = 31;
while ($flexbits > 0) {
// Get the characters at this position
$orig_first = substr($addr_hex_first, $pos, 1);
$orig_last = substr($addr_hex_last, $pos, 1);
// Convert them to an integer
$origval_first = hexdec($orig_first);
$origval_last = hexdec($orig_last);
// First address: calculate the subnet mask. min() prevents the comparison from being negative
$mask = 0xf << (min(4, $flexbits));
// AND the original against its mask
$new_val_first = $origval_first & $mask;
// Last address: OR it with (2^flexbits)-1, with flexbits limited to 4 at a time
$new_val_last = $origval_last | (pow(2, min(4, $flexbits)) - 1);
// Convert them back to hexadecimal characters
$new_first = dechex($new_val_first);
$new_last = dechex($new_val_last);
// And put those character back in their strings
$addr_hex_first = substr_replace($addr_hex_first, $new_first, $pos, 1);
$addr_hex_last = substr_replace($addr_hex_last, $new_last, $pos, 1);
// We processed one nibble, move to previous position
$flexbits -= 4;
$pos -= 1;
}
// Convert the hexadecimal strings to a binary string
$addr_bin_first = hex2bin($addr_hex_first);
$addr_bin_last = hex2bin($addr_hex_last);
// And create an IPv6 address from the binary string
$addr_str_first = inet_ntop($addr_bin_first);
$addr_str_last = inet_ntop($addr_bin_last);
// Report to user
echo "Prefix: $prefix\n";
echo "First: $addr_str_first\n";
echo "Last: $addr_str_last\n";
Outputs:
Prefix: 2001:db8:abc:1403::/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 - mike#bakeryphp.com
* #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 - mike#bakeryphp.com
* #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"
Well, for posterity, I'm adding my code here. And also as a thanks to you guys who helped me nail this down as I needed it for an ipv6/ip2country script.
It's slightly inspired by code posted here by #mikemacintosh and #Sander Steffann, slightly improved (whishful thinking) and returns a nice object packing all the data you do/don't need:
/**
* This:
* <code>
* Ipv6_Prefix2Range('2001:43f8:10::/48');
* </code>
* returns this:
* <code>
* object(stdClass)#2 (4) {
* ["Prefix"]=>
* string(17) "2001:43f8:10::/48"
* ["FirstHex"]=>
* string(32) "200143f8001000000000000000000000"
* ["LastHex"]=>
* string(32) "200143f80010ffffffffffffffffffff"
* ["MaskHex"]=>
* string(32) "ffffffffffff00000000000000000000"
* // Optional bin equivalents available
* }
* </code>
*
* Tested against:
* #link https://www.ultratools.com/tools/ipv6CIDRToRange
*
* #param string $a_Prefix
* #param bool $a_WantBins
* #return object
*/
function Ipv6_Prefix2Range($a_Prefix, $a_WantBins = false){
// Validate input superficially with a RegExp and split accordingly
if(!preg_match('~^([0-9a-f:]+)[[:punct:]]([0-9]+)$~i', trim($a_Prefix), $v_Slices)){
return false;
}
// Make sure we have a valid ipv6 address
if(!filter_var($v_FirstAddress = $v_Slices[1], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)){
return false;
}
// The /## end of the range
$v_PrefixLength = intval($v_Slices[2]);
if($v_PrefixLength > 128){
return false; // kind'a stupid :)
}
$v_SuffixLength = 128 - $v_PrefixLength;
// Convert the binary string to a hexadecimal string
$v_FirstAddressBin = inet_pton($v_FirstAddress);
$v_FirstAddressHex = bin2hex($v_FirstAddressBin);
// Build the hexadecimal string of the network mask
// (if the manually formed binary is too large, base_convert() chokes on it... so we split it up)
$v_NetworkMaskHex = str_repeat('1', $v_PrefixLength) . str_repeat('0', $v_SuffixLength);
$v_NetworkMaskHex_parts = str_split($v_NetworkMaskHex, 8);
foreach($v_NetworkMaskHex_parts as &$v_NetworkMaskHex_part){
$v_NetworkMaskHex_part = base_convert($v_NetworkMaskHex_part, 2, 16);
$v_NetworkMaskHex_part = str_pad($v_NetworkMaskHex_part, 2, '0', STR_PAD_LEFT);
}
$v_NetworkMaskHex = implode(null, $v_NetworkMaskHex_parts);
unset($v_NetworkMaskHex_part, $v_NetworkMaskHex_parts);
$v_NetworkMaskBin = inet_pton(implode(':', str_split($v_NetworkMaskHex, 4)));
// We have the network mask so we also apply it to First Address
$v_FirstAddressBin &= $v_NetworkMaskBin;
$v_FirstAddressHex = bin2hex($v_FirstAddressBin);
// Convert the last address in hexadecimal
$v_LastAddressBin = $v_FirstAddressBin | ~$v_NetworkMaskBin;
$v_LastAddressHex = bin2hex($v_LastAddressBin);
// Return a neat object with information
$v_Return = array(
'Prefix' => "{$v_FirstAddress}/{$v_PrefixLength}",
'FirstHex' => $v_FirstAddressHex,
'LastHex' => $v_LastAddressHex,
'MaskHex' => $v_NetworkMaskHex,
);
// Bins are optional...
if($a_WantBins){
$v_Return = array_merge($v_Return, array(
'FirstBin' => $v_FirstAddressBin,
'LastBin' => $v_LastAddressBin,
'MaskBin' => $v_NetworkMaskBin,
));
}
return (object)$v_Return;
}
I like functions and classes and dislike non-reusable code where reusable functionality is implemented.
PS: If you find issues with it, please get back to me. I'm far from an expert in IPv6.
Related
I am able to do this with IPv4 using code snippets from various online sources. I was wondering if there was a way to do it with IPv6.
Basically I just need a form that I can enter an IPv6 address and prefix (ex: f080:42d2:581a::0/68) and it calculates the network address, first useable address, last useable address, and broadcast address. Then just prints to screen. Not looking to store it in a database or anything yet.
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
This is a fix to the accepted answer, which incorrectly assumes the "first address" should be identical to the inputted string. Rather, it needs to have its value modified via an AND operator against its mask.
To demonstrate the problem, consider this example input: 2001:db8:abc:1403::/54
Expected result:
First: 2001:db8:abc:1400::
Actual result:
First: 2001:db8:abc:1403::
The relevant math to calculate the mask for a given 4-bit sequence is:
// Calculate the subnet mask. min() prevents the comparison from being negative
$mask = 0xf << (min(4, $flexbits));
// AND the original against its mask
$newval = $origval & $mask;
Full code
<?php
/*
* This is definitely not the fastest way to do it!
*/
// An example prefix
$prefix = '2001:db8:abc:1403::/54';
// Split in address and prefix length
list($addr_given_str, $prefixlen) = explode('/', $prefix);
// Parse the address into a binary string
$addr_given_bin = inet_pton($addr_given_str);
// Convert the binary string to a string with hexadecimal characters
$addr_given_hex = bin2hex($addr_given_bin);
// Overwriting first address string to make sure notation is optimal
$addr_given_str = inet_ntop($addr_given_bin);
// Calculate the number of 'flexible' bits
$flexbits = 128 - $prefixlen;
// Build the hexadecimal strings of the first and last addresses
$addr_hex_first = $addr_given_hex;
$addr_hex_last = $addr_given_hex;
// We start at the end of the string (which is always 32 characters long)
$pos = 31;
while ($flexbits > 0) {
// Get the characters at this position
$orig_first = substr($addr_hex_first, $pos, 1);
$orig_last = substr($addr_hex_last, $pos, 1);
// Convert them to an integer
$origval_first = hexdec($orig_first);
$origval_last = hexdec($orig_last);
// First address: calculate the subnet mask. min() prevents the comparison from being negative
$mask = 0xf << (min(4, $flexbits));
// AND the original against its mask
$new_val_first = $origval_first & $mask;
// Last address: OR it with (2^flexbits)-1, with flexbits limited to 4 at a time
$new_val_last = $origval_last | (pow(2, min(4, $flexbits)) - 1);
// Convert them back to hexadecimal characters
$new_first = dechex($new_val_first);
$new_last = dechex($new_val_last);
// And put those character back in their strings
$addr_hex_first = substr_replace($addr_hex_first, $new_first, $pos, 1);
$addr_hex_last = substr_replace($addr_hex_last, $new_last, $pos, 1);
// We processed one nibble, move to previous position
$flexbits -= 4;
$pos -= 1;
}
// Convert the hexadecimal strings to a binary string
$addr_bin_first = hex2bin($addr_hex_first);
$addr_bin_last = hex2bin($addr_hex_last);
// And create an IPv6 address from the binary string
$addr_str_first = inet_ntop($addr_bin_first);
$addr_str_last = inet_ntop($addr_bin_last);
// Report to user
echo "Prefix: $prefix\n";
echo "First: $addr_str_first\n";
echo "Last: $addr_str_last\n";
Outputs:
Prefix: 2001:db8:abc:1403::/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 - mike#bakeryphp.com
* #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 - mike#bakeryphp.com
* #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"
Well, for posterity, I'm adding my code here. And also as a thanks to you guys who helped me nail this down as I needed it for an ipv6/ip2country script.
It's slightly inspired by code posted here by #mikemacintosh and #Sander Steffann, slightly improved (whishful thinking) and returns a nice object packing all the data you do/don't need:
/**
* This:
* <code>
* Ipv6_Prefix2Range('2001:43f8:10::/48');
* </code>
* returns this:
* <code>
* object(stdClass)#2 (4) {
* ["Prefix"]=>
* string(17) "2001:43f8:10::/48"
* ["FirstHex"]=>
* string(32) "200143f8001000000000000000000000"
* ["LastHex"]=>
* string(32) "200143f80010ffffffffffffffffffff"
* ["MaskHex"]=>
* string(32) "ffffffffffff00000000000000000000"
* // Optional bin equivalents available
* }
* </code>
*
* Tested against:
* #link https://www.ultratools.com/tools/ipv6CIDRToRange
*
* #param string $a_Prefix
* #param bool $a_WantBins
* #return object
*/
function Ipv6_Prefix2Range($a_Prefix, $a_WantBins = false){
// Validate input superficially with a RegExp and split accordingly
if(!preg_match('~^([0-9a-f:]+)[[:punct:]]([0-9]+)$~i', trim($a_Prefix), $v_Slices)){
return false;
}
// Make sure we have a valid ipv6 address
if(!filter_var($v_FirstAddress = $v_Slices[1], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)){
return false;
}
// The /## end of the range
$v_PrefixLength = intval($v_Slices[2]);
if($v_PrefixLength > 128){
return false; // kind'a stupid :)
}
$v_SuffixLength = 128 - $v_PrefixLength;
// Convert the binary string to a hexadecimal string
$v_FirstAddressBin = inet_pton($v_FirstAddress);
$v_FirstAddressHex = bin2hex($v_FirstAddressBin);
// Build the hexadecimal string of the network mask
// (if the manually formed binary is too large, base_convert() chokes on it... so we split it up)
$v_NetworkMaskHex = str_repeat('1', $v_PrefixLength) . str_repeat('0', $v_SuffixLength);
$v_NetworkMaskHex_parts = str_split($v_NetworkMaskHex, 8);
foreach($v_NetworkMaskHex_parts as &$v_NetworkMaskHex_part){
$v_NetworkMaskHex_part = base_convert($v_NetworkMaskHex_part, 2, 16);
$v_NetworkMaskHex_part = str_pad($v_NetworkMaskHex_part, 2, '0', STR_PAD_LEFT);
}
$v_NetworkMaskHex = implode(null, $v_NetworkMaskHex_parts);
unset($v_NetworkMaskHex_part, $v_NetworkMaskHex_parts);
$v_NetworkMaskBin = inet_pton(implode(':', str_split($v_NetworkMaskHex, 4)));
// We have the network mask so we also apply it to First Address
$v_FirstAddressBin &= $v_NetworkMaskBin;
$v_FirstAddressHex = bin2hex($v_FirstAddressBin);
// Convert the last address in hexadecimal
$v_LastAddressBin = $v_FirstAddressBin | ~$v_NetworkMaskBin;
$v_LastAddressHex = bin2hex($v_LastAddressBin);
// Return a neat object with information
$v_Return = array(
'Prefix' => "{$v_FirstAddress}/{$v_PrefixLength}",
'FirstHex' => $v_FirstAddressHex,
'LastHex' => $v_LastAddressHex,
'MaskHex' => $v_NetworkMaskHex,
);
// Bins are optional...
if($a_WantBins){
$v_Return = array_merge($v_Return, array(
'FirstBin' => $v_FirstAddressBin,
'LastBin' => $v_LastAddressBin,
'MaskBin' => $v_NetworkMaskBin,
));
}
return (object)$v_Return;
}
I like functions and classes and dislike non-reusable code where reusable functionality is implemented.
PS: If you find issues with it, please get back to me. I'm far from an expert in IPv6.
I have a Hex String which reads 18000000 this String is in Host byte order (Little endian) and I need to convert it to Network byte order (Big endian). The resultant Hex String will be 00000018.
To summarize I need to convert
18000000 to 00000018
How do I achieve this in PHP?
You can use pack / unpack functions to convert endianness:
/**
* Convert $endian hex string to specified $format
*
* #param string $endian Endian HEX string
* #param string $format Endian format: 'N' - big endian, 'V' - little endian
*
* #return string
*/
function formatEndian($endian, $format = 'N') {
$endian = intval($endian, 16); // convert string to hex
$endian = pack('L', $endian); // pack hex to binary sting (unsinged long, machine byte order)
$endian = unpack($format, $endian); // convert binary sting to specified endian format
return sprintf("%'.08x", $endian[1]); // return endian as a hex string (with padding zero)
}
$endian = '18000000';
$big = formatEndian($endian, 'N'); // string "00000018"
$little = formatEndian($endian, 'V'); // string "18000000"
To learn more about pack format take a look at http://www.php.net/manual/en/function.pack.php
I just stumbled on this answer while searching for something, but I ended up doing something a lot simpler (since I knew my string was always going to have a leading zero if necessary to align to a two character boundary):
$littlendian = join(array_reverse(str_split($bigendian), 2)));
Obviously if your bigendian input isn't well formatted like that, this trick isn't going to work directly :)
another way
$input = 18000000;
$length=strlen((string)$input);
$sum=0;
$numbers="";
while ($length>0) {
$number = $input % 10;
$numbers.= $number;
$input = $input/10;
$length--;
}
echo $numbers;
Try this:
$result=bin2hex( implode( array_reverse( str_split( hex2bin($src) ) ) ) );
I have a list of IP ranges like
$whitelist=array('50*','202.16*','123.168*',');
I want to block all other traffic from seeing the page.
I've tried
if(in_array($_SERVER['REMOTE_ADDR'],$whitelist)){
//display page
}
in_array doesn't use regexs to compare. Also your regex is incorrect the * is a quantifier. That allows zero or more of the previous character.
Try:
$whitelist=array('50\..*','202\.16\..*','123\.168\..*');
if(preg_match('/^(' . implode('|', $whitelist) . ')/', $_SERVER['REMOTE_ADDR'])){
The .* is allowing anything (pretty much(see s modifier http://php.net/manual/en/reference.pcre.pattern.modifiers.php), . is any character and then paired with that quantifier previously mentioned). The ^ is the start of the string. \. is a literal .. The | is an or.
Demo: https://eval.in/571019
Regex Demo: https://regex101.com/r/dC5uI0/1
This should work for you:
you can loop through white list ip and trim space and * (if found from right).
There after using substr you can cut the IP address of the same length of whitelist ip in loop, and compare both.
$whitelists = array('50*','202.16*','123.168*');
foreach($whitelists as $whitelist){
$whitelist = rtrim($whitelist, "*\r\n\t\0 ");
if(substr($_SERVER['REMOTE_ADDR'], 0, strlen($whitelist)) == $whitelist) {
$match = true;
break;
}
}
echo $match ? 'Match' : 'Not Match';
as stated by #chris85, in_array doesn't use regexs.
To do such a thing, you could simply use a loop like this:
if(preg_match('/^(' . implode('|', $whitelist) . ')/i', $_SERVER['REMOTE_ADDR'])){
// Do your stuff
}
Your '*' doesn't work as you thought.. That's ok:
$whitelist=array('50\.*','202.16\.*','123.168\.*');
Hope this helps someone. Seeing as how you didn't specifically ask about regular expressions, and since the topic is about matching IP addresses, I thought I'd put this out there so that it may help someone with a similar issue.
Server software usually strives to be as quick and efficient as possible; matching IP addresses is usually done arithmetically. That being said, I'll go over a fast way to do exactly what you're trying to do before going over a possible alternative.
If you're simply performing a wild card match on IP address strings, I would suggest that you use this method. It has been tailored to your use case, but I will include the simple matching function by itself.
For contrast, I've also included the sample output with execution times using this method compared to using PHP's RegEx functions (which are the way to go for more complex pattern matching)
NOTES:
I use the functions referenced herein for very specific purposes. They are applicable to this scenario because IP addresses don't have '*' characters in them. As they are written, if the variable you're testing has a '*' character in it, it will only match against a wild card character so there is a slight possibility for loss of information there.
If you're writing a command-line daemon or your process will use the same list of IPs to check against multiple times during its lifetime, it is beneficial to use the RegEx library. The small speed benefit to using my method here is only won when having to initially load and prepare the IP list RegEx for first use.
You can move the code from "wildcard_match()" to inside of "match_ip() for further benefit, avoiding the overhead of another function call.
Code (you can copy and paste):
<?php
/**
* This function compares a string ("$test") to see if it is
* equal to another string ("$wild"). An '*' in "$wild" will
* match any characters in "$test".
*
* #param string $wild - The string to compare against. This may
* be either an exact character string to match, or a string
* with a wild card ('*') character that will match any character(s)
* found in "$test".
*
* #param string $test - A character string we're comparing against
* "$wild" to determine if there is a match.
*
* #return bool Returns TRUE if "$test" is either an exact match to
* "$wild", or it fits the bill taking any wild card characters into
* consideration.
*
**/
function wildcard_match( $pattern, $test ) {
$p = 0;
$a_name = explode("*", $pattern);
$segs = count($a_name);
$max_seg = ($segs-1);
$plen = 0;
$test_len = strlen($test);
for ($i = 0; $i < $segs; $i++) {
$part = $a_name[$i];
$plen = strlen($part);
if ($plen === 0) {
if ($i === $max_seg) return true;
continue;
}
$p = strpos($test, $part, $p);
if ($p === false) {
return false;
}
$p+=$plen;
}
if ($p===$test_len) {
return true;
}
return false;
}
/**
* Function to quickly traverse an array of whole, or
* wild card IPv4 addresses given in "$whitelist" and
* determine if they match the given whole IPv4
* address in "$test".
*
* #param array $whitelist - An array of IPv4 addresses, either
* whole, or containing an '*' character wherein any character(s)
* in "$test" will match.
*
* #param string $test - A complete string (dot-decimal) IPv4
* address to compare against the contents of the array given in
* parameter one ("$whitelist").
*
* #return bool Returns TRUE, if the IPv4 address given in "$test" was
* matched to an IPv4 address or IPv4 wild card pattern in the array
* given in parameter one ("$whitelist").
*
**/
function match_ip( $whitelist, $test ) {
foreach ($whitelist as $w) {
if (wildcard_match($w, $test)) return true;
}
return false;
}
/* The array of IP addresses we're going to validate */
$check_array = array("50.245.1.9", "35.125.25.255", "202.16.15.25");
/* The array as given in your example (minus the extra ' at the end) */
$whitelist1=array('50*','202.16*','123.168*');
/* An array for RegEx matching */
$whitelist2=array('50\..*','202\.16\..*','123\.168\..*');
microtime(true); /* Execute this once to make sure its module is loaded */
echo "Giving PHP a second to get its ducks in a row...\n";
usleep(1000000); /** Give PHP a second to load and prepare */
$st = microtime(true);
foreach ($check_array as $c) {
if (match_ip($whitelist1, $c)) {
echo "$c....Match!\n";
} else {
echo "$c....No match!\n";
}
}
$dt = microtime(true)-$st;
echo "Time: $dt\n\n\n\n";
$st = microtime(true);
foreach ($check_array as $c) {
if(preg_match('/^(' . implode('|', $whitelist2) . ')/', $c)){
echo "$c....Match!\n";
} else {
echo "$c....No Match!\n";
}
}
$dt = microtime(true)-$st;
echo "Time: $dt\n\n";
The output from this is:
Giving PHP a second to get its ducks in a row...
50.245.1.9....Match!
35.125.25.255....No match!
202.16.15.25....Match!
Time 1: 0.00027704238891602
50.245.1.9....Match!
35.125.25.255....No Match!
202.16.15.25....Match!
Time 2: 0.00040698051452637
The first result set is from the function "match_ip()", the second result set is from firing up the RegEx library.
Now, a possible better solution to wild card matching IPs would be to employ an array of IP addresses in CIDR notation in your array. Often times, you want IP traffic to be allowed from a specific network or range of IP addresses.
There are quite a few assumptions here, but for example (using your "$whitelist" array):
'50*' might be construed as "I want all IP addressess from 50.xxx.xxx.xxx to be allowed access.
In that case, you'd specify the format "50.0.0.0/8". (The "0"s after the "50." can be any number. They will be completely ignored because of the "/8".)
xxx.xxx.xxx.xxx
| | | |
8 16 24 32
An IPv4 address is computationally 32 bits, so above, you're saying that all you care about is the first 8 bits matching.
'123.168*' would be "123.168.0.0/16"
"101.23.54.0/24" would allow all IP addresses starting with "101.23.54" access.
"44.32.240.10/32" would only allow the IP address "44.32.240.10" access. No range.
So you could do something like this:
<?php
/**
* Determines if the two given IPv4 addresses
* are equal, or are on the same network using
* the given number of "$mask" bits.
*
* #param string $ip1 - The first string dot-decimal IPv4
* address.
*
* #param string $ip1 - The second string dot-decimal IPv4
* address.
*
* #param int $mask - The number of bits in the mask.
*
* #return bool Returns TRUE if they match after being
* masked, or FALSE if not.
*
*/
function ip_match( $ip1, $ip2, $mask) {
$mask = (int)$mask;
$ip1 = ip2long($ip1);
$ip2 = ip2long($ip2);
if ($ip1 === false || $ip2 === false) return false;
if ($mask < 1 || $mask > 32) return false;
$mask = (0x00000000FFFFFFFF & 0x00000000FFFFFFFF << (32-$mask));
if ( ($ip1 & $mask) === ($ip2 & $mask) ) {
return true;
}
return false;
}
/**
* Takes an array of string (CIDR) network representations and
* sorts them into an array used later for checking against IP
* addresses.
*
* #param array $cidr_array - An array of IP addressess in
* CIDR notation e.g. 192.168.1.1/24
*
* #return array Returns an array of objects with the following
* properties:
* 'ip' - The string (dot-decimal) IP address that
* has been numerically verified for use
* in comparisons later.
*
* 'mask' - The number of bits used for creating
* the subnet mask against which IP
* addresses will be compared.
*
**/
function make_whitelist( $cidr_array ) {
$wl = array();
$lip = 0;
$bm = 0;
$spos = 0;
if (!is_array($cidr_array)) return false;
foreach ($cidr_array as $ip) {
$spos = strpos($ip, "/");
if ($spos === false) {
$bm = 32; /* If there's no "/", assume
* that we want an EXACT IP
* address. Hence the 32 bit
* mask
**/
} else {
$bm = (int)substr($ip, ($spos+1));
$ip = substr($ip, 0, $spos++);
}
$lip = ip2long($ip); /* Using this here to check IP validity
* before storing it in the array...
* We use ip2long() later for comparisons.
*
* You can store it this way - as a long -
* instead of as a string (I do) to
* use less memory if you wish.
*
**/
if ($bm === 0) continue; /* A bit mask of ZERO will block
* ALL IP addresses, skip it
* for the example.
**/
if ($lip === false) continue; /* If it's an invalid IP, skip it,
* you could optionally try to
* resolve it as a hostname using
* gethostbyname() or gethostbynamel()
* here...
**/
array_push($wl, (object)array('ip'=>$ip, 'mask'=>$bm));
}
return $wl;
}
$whitelist = make_whitelist(array("50.0.0.0/8", "202.16.0.0/16", "123.168.0.0/16", "1.1.1.1"));
$ips_to_check = array("50.1.174.41", "42.123.100.23", "123.168.4.79", "1.1.1.2", "1.1.1.1");
foreach ($ips_to_check as $ip) {
foreach ($whitelist as $w) {
if (ip_match($ip, $w->ip, $w->mask)) {
echo "\"$ip\" is allowed!\n";
continue 2;
}
}
echo "\"$ip\" is NOT allowed!\n";
}
I know this is a lot, but there is plenty here for people to think about, find while searching, and hopefully be able to use to make their lives easier!
I am using Laravel 4 and I am looking for a system to generate simple short alphanumeric codes, kinda like what Steam does when you login from a new machine, they send you an email with some text like this:
Someone has tried to log in into your account from an unknown device.
If this is really you, please input this code to authenticate the
access:
ZT27K
Thank you
Is there some sort of random, short codes generator in Laravel?
Yup! A bit down on the helpers documentation page is the str_random() function, which can be used anywhere like so:
$string = str_random(5);
(Note: it defaults to 16 if its length argument is left out.)
This is how I implemented mine, thoughStr::random($length) would be better:
/**
* #param string prefix Any desired character to prepend on the generated code
* #param int length a number indicating the number of characters in the code
* #param string factor a string of characters to be mixed to generate the code
* #return string string with random characters based the provided length
*/
function generateCode($prefix = '', $length = 8, $factor = null)
{
$s = "9/A/B/0/C/2/D/E/3/F/G/H/I//J/4/K/L/M/N/O/5/P/7/R/S/8/T/U/V/6/W/X/Y/Z";
if ($factor === null)
$factor = $s;
$rdm_text_arr = explode("/", $factor, strlen($factor));
$code_array = array();
array_push($code_array, $prefix);
for ($i = 1; $i <= $length; $i++) :
array_push($code_array, $rdm_text_arr[mt_rand(0, count($rdm_text_arr) - 1)]);
endfor;
return implode("", $code_array);
}
and this is a sample output: WJH3Z0MX
You may also try Str::random(8) which gives 8 random characters.
Sander Steffann mentioned in a previous question of mine:
Addresses like 0000:0000:0000:0000:0000:0000:192.168.0.1 are written as
0000:0000:0000:0000:0000:0000:c0a8:0001 which is exactly the same address
but in hex notation.
How do I detect in PHP if an address was written like eg.: ::0000:192.168.0.1 or 0000::0000:192.168.0.1 or 0000:0000:0000:0000:0000:0000:192.168.0.1 etc.? Is it enough to check if an IP-based string has '.' AND ':' ?
And how do I change this to the full string 0000:0000:0000:0000:0000:0000:c0a8:0001?
Am I correct, to change this to IPv4 will be something like:
<?php
$strIP = '0000:0000:0000:0000:0000:0000:192.168.0.1';
$strResult = substr($strIP, strrpos($strIP, ':'));
echo $strResult; //192.168.0.1 ?
?>
... or are correct IP string representations more complex than what this snippet could do?
I can't believe I wrote this all out in one go and it worked the first time.
$strIP = '0000:0000:0000:0000:0000:0000:192.168.0.1';
$arrIP = explode(':', $strIP);
if( preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/', $arrIP[count($arrIP)-1]) ) {
$ip4parts = explode('.', $arrIP[count($arrIP)-1]);
$ip6trans = sprintf("%02x%02x:%02x%02x", $ip4parts[0], $ip4parts[1], $ip4parts[2], $ip4parts[3]);
$arrIP[count($arrIP)-1] = $ip6trans;
$strIP = implode(':', $arrIP);
}
echo $strIP; //output: 0000:0000:0000:0000:0000:0000:c0a8:0001
Basically:
Explode the string on :
Check if the last quad is formatted like an IP4 address
Explode the last quad on .
Re-print the IP4 octets into two hex quads
Replace the IP4 quad with the new ones
Implode the array on :.
Your best bet is to not do this manually, but instead call inet_pton to get a binary representation, and then convert that to the format you wish to have.
$foo = inet_pton("::1");
for ($i = 0 ; $i < 8 ; $i++)
$arr[$i] = sprintf("%02x%02x", ord($foo[$i * 2]), ord($foo[$i * 2 + 1]));
$addr = implode(":", $arr);
First of all: why would you care how the address is written? inet_pton() will parse all variations for you and give you a consistent result, which you can then transform into binary, hex, or whatever you want.
All the code for converting things like ::192.168.0.1 to 0000:0000:0000:0000:0000:0000:c0a8:0001 was actually in my post. That's exactly what my example function does.
If you feed 0000:0000:0000:0000:0000:0000:192.168.0.1 to inet_pton() and then to inet_ntop() you'll get the canonical IPv6 notation, which is ::192.168.0.1 in this case. If that string begins with :: and the rest contains no : and three dots then you can be pretty sure it's an IPv4 address ;-)
To combine the answer to your previous question with this question:
function expand_ip_address($addr_str) {
/* First convert to binary, which also does syntax checking */
$addr_bin = #inet_pton($addr_str);
if ($addr_bin === FALSE) {
return FALSE;
}
$addr_hex = bin2hex($addr_bin);
/* See if this is an IPv4-Compatible IPv6 address (deprecated) or an
IPv4-Mapped IPv6 Address (used when IPv4 connections are mapped to
an IPv6 sockets and convert it to a normal IPv4 address */
if (strlen($addr_bin) == 16
&& substr($addr_hex, 0, 20) == str_repeat('0', 20)) {
/* First 80 bits are zero: now see if bits 81-96 are either all 0 or all 1 */
if (substr($addr_hex, 20, 4) == '0000')
|| substr($addr_hex, 20, 4) == 'ffff')) {
/* Remove leading bits so only the IPv4 bits remain */
$addr_bin = substr($addr_hex, 12);
}
}
/* Then differentiate between IPv4 and IPv6 */
if (strlen($addr_bin) == 4) {
/* IPv4: print each byte as 3 digits and add dots between them */
$ipv4_bytes = str_split($addr_bin);
$ipv4_ints = array_map('ord', $ipv4_bytes);
return vsprintf('%03d.%03d.%03d.%03d', $ipv4_ints);
} else {
/* IPv6: print as hex and add colons between each group of 4 hex digits */
return implode(':', str_split($addr_hex, 4));
}
}