In the rfc4291
said, that first 80 bits of this type of Ipv4 mapped Ipv6 address is 0.
How to check that with PHP?
I need more quick and safe way to do that instead of regexp.
The easiest way is to convert the address from printable to binary form with inet_pton. That will give you a string where every charter corresponds to 8 bits of the address. Checking if the first 80 bits are zero is then as simple as checking the first 10 characters of the returned string:
$addr = '::10.1.2.3';
$bytes = inet_pton($addr);
if (substr($bytes, 0, 10) == "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") {
echo "Yes\n";
} else {
echo "No\n";
}
Related
I need to convert IPV6 address to 15 chars length integer so I could use IP2LOCATION database?
This is the Database "IP2LOCATION-LITE-DB1.IPV6.CSV", that I downloaded from here http://download.ip2location.com/lite/
I try to use this function, but it gives me very long string:
function ipv6_numeric($ip) {
$binNum = '';
foreach (unpack('C*', inet_pton($ip)) as $byte) {
$binNum .= str_pad(decbin($byte), 8, "0", STR_PAD_LEFT);
}
return base_convert(ltrim($binNum, '0'), 2, 10);
}
ipv6_numeric('fe80:0:0:0:202:b3ff:fe1e:8329')
return "338288524927261046600406220626806860202"
I found this function:
function Dot2LongIP($ipv6) {
return (string) gmp_import(inet_pton($ipv6));
}
at this URL: https://lite.ip2location.com/faqs
under the question: "How do I convert an IPv6 Address to an IP Number?"
But your ipv6 address fe80:0:0:0:202:b3ff:fe1e:8329 also returns a long number: 338288524927261089654163772891438416681. Note that this result is different from what you have.
As to the length of the result: If you actually look in the CSV file, as suggested by Nigel Ren in a comment, you will see that there are long numbers there as well.
So, this long number is correct.
I'm using PHP version 5.2.17, and I see that the following works as expected:
$x = inet_pton('::F');
$y = inet_ntop($x);
print "::F -> $y\n";
Output: ::F -> ::f
But the following does not:
$a = inet_pton('::FEEF:1886');
$b = inet_ntop($a);
print "::FEEF:1886 -> $b\n";
Output: ::FEEF:1886 -> ::254.239.24.134
I would have expected the second code snippet to produce this output:
::FEEF:1886 -> ::feef:1886
What is it about the IPv6 address ::FEEF:1886 that makes PHP think it is really an IPv4 address? The inet_ntop/inet_pton conversion works correctly with other addresses having 0 in the "high" 96 bits (e.g. ::F).
EDIT: My first thought was that this might be a bug in my version of PHP, but using this online PHP sandbox I see the same behavior for PHP versions up through 5.6.2. So either this is deliberate (in which case I would dearly like to know the reason for this behavior) or a bug which persists in modern versions of PHP.
ADDENDUM: I opened PHP Bug 69232 on March 12, 2015 for this apparent inconsistency in the behavior of inet_ntop() for addresses in ::/96.
The textual representation of IPv6 addresses permit for multiple different valid representations of every IPv6 address.
This means that all of these valid textual representations of an IPv6 address each map to the same binary 128 bit string when passed through inet_pton.
However when converting the binary 128 bit string to textual representation using inet_ntop, it can obviously only output one of the many valid strings representing that IP address. The one it chose is called the canonical representation.
It is always valid to write the last 32 bits of an IPv6 address using IPv4 notation. However only a few classes of IPv6 addresses used that format as their canonical representation.
::/96 has been deprecated, but that just means those addresses are not supposed to be used anymore, it doesn't affect how they are treated by inet_pton and inet_ntop.
::ffff:0.0.0.0/96 is another prefix, which use IPv4 notation in their canonical representation. That prefix is used for IPv4 compatibility in the socket API, but those are never send on the wire, because they are for situations where the traffic on the wire will be IPv4.
What you're looking at is an IPv4 address being represented (incorrectly) as an IPv6 address. This practice was officially deprecated in 2006 by RFC 4291:
The "IPv4-Compatible IPv6 address" is now deprecated because the
current IPv6 transition mechanisms no longer use these addresses.
New or updated implementations are not required to support this
address type.
Try this out :
function _inet_ntop($ip) {
if (strlen($ip) == 4) { // For IPv4
list(, $ip) = unpack('N', $ip);
$ip = long2ip($ip);
}
elseif(strlen($ip) == 16) { // For IPv6
$ip = bin2hex($ip);
$ip = substr(chunk_split($ip, 4, ':'), 0, -1);
$ip = explode(':', $ip);
$res = '';
foreach($ip as $index => $seg) {
while ($seg {0} == '0')
$seg = substr($seg, 1);
if ($seg != '') {
$res .= $seg;
if ($index < count($ip) - 1)
$res .= $res == '' ? '' : ':';
} else {
if (strpos($res, '::') === false)
$res .= ':';
}
}
$ip = $res;
}
return $ip;
}
And you can call this function instead of inet_ntop :
$a = inet_pton('::FEEF:1886');
$b = _inet_ntop($a);
print "::FEEF:1886 -> $b\n";
// Output => ::FEEF:1886 -> ::feef:1886
$x = inet_pton('::F');
$y = _inet_ntop($x);
print "::F -> $y\n";
// Output => ::F -> ::f
To summarize what I've head so far in the answers & comments:
IPv6 addresses have a canonical format which is what inet_ntop() returns.
The ::/96 address range is deprecated, but ::ffff/80 is not.
Although it would make sense for all ::/96 addresses to be rendered as ::/IPv4-address by inet_ntop() it appears that inet_ntop() renders ::/112 addresses and "higher" in ::/96 as ::/IPv4-dotted-quad (e.g. ::254.239.24.134) and renders ::/96 addresses "lower" than ::/112 as "normal" IPv6 addresses.
If you want inet_ntop() to render all IPv6 addresses the same way (i.e. 8 hex words with the usual zero compression rules) then you need to write your own method to achieve this.
My own workaround is to extend inet_ntop() by rewriting any IPv4 dotted quads as hexwords (and I exploded the logic into multiple methods to make it easier for me to keep track of what I was doing):
function _inet_ntop($addr) {
return fix_ipv4_compatible_ipv6(inet_ntop($addr));
}
/**
* If $str looks like ::/IPv4-dotted-quad then rewrite it as
* a "pure" IPv6 address, otherwise return it unchanged.
*/
function fix_ipv4_compatible_ipv6($str) {
if (
($str[0] == ':') &&
($str[1] == ':') &&
preg_match('/^::(\S+\.\S+)$/', $str, $match)
) {
$chunks = explode('.', $match[1]);
return self::ipv4_zones_to_ipv6(
$chunks[0],
$chunks[1],
$chunks[2],
$chunks[3]
);
} else {
return $str;
}
}
/**
* Return a "pure" IPv6 address printable string representation
* of the ::/96 address indicated by the 4 8-bit "zones" of an
* IPv4 address (e.g. (254, 239, 24, 134) -> ::feef:1886).
*/
function ipv4_zones_to_ipv6($q1, $q2, $q3, $q4) {
if ($q1 == 0) {
if ($q2 == 0) {
if ($q3 == 0) {
if ($q4 == 0) {
return '::0';
} else {
return '::' . self::inflate_hexbit_pair($q4);
}
} else {
return '::' . self::inflate_hex_word($q3, $q4);
}
} else {
return '::' . self::inflate_hexbit_pair($q2) . ':' . self::inflate_hex_word($q3, $q4);
}
} else {
return '::' . self::inflate_hex_word($q1, $q2) . ':' . self::inflate_hex_word($q3, $q4);
}
}
/**
* Convert two 8-bit IPv4 "zones" into a single 16-bit hexword,
* stripping leading 0s as needed, e.g.:
* (254, 239) -> feef
* (0,1) -> 1
*/
function inflate_hex_word($hb1, $hb2) {
$w = self::inflate_hexbit_pair($hb1) . self::inflate_hexbit_pair($hb2);
return ltrim($w, '0');
}
/**
* Convert one 8-bit IPv4 "zone" into two hexadecimal digits,
* (hexits) padding with a leading zero if necessary, e.g.:
* 254 -> fe
* 2 -> 02
*/
function inflate_hexbit_pair($hb) {
return str_pad(dechex($hb), 2, '0', STR_PAD_LEFT);
}
Although arguably much less elegant than the _inet_ntop() function proposed by JC Sama, it runs about 25% faster over my (essentially random) test cases.
Answers provided by kasperd, diskwuff, and JC Sama offer both helpful information and workarounds which are likely to be useful to other SO readers, so I have upvoted them all. But they do not address my original question directly, so I'm adding this answer:
The behavior of the PHP function inet_pton() is correct. The problem is that inet_ntop() does not treat IPv6 address in ::/96 consistently. This is a bug in PHP.
I have a starting IPv4 IP address 5.39.28.128 (or ::ffff:5.39.28.128) and I have the IPv6 network mask length 122, how can I calculate the last IP in the range?
I believe I need to convert the start IP to binary, which I'm doing like below, I don't know where to go from there to get the end IP.
$ipNumber = ip2long('5.39.28.128');
$ipBinary = decbin($ipNumber);
echo $ipBinary; // 101001001110001110010000000
The reason is I'm importing the MaxMind GeoIP database in CSV format into a MySQL database (so MySQL functions can be used if needed). MaxMind no longer provide the end IP, in favour of providing the start IP and the IPv6 network mask length instead.
Here you are. I've copied the inet_to_bits function from this response to another question.
<?php
function inet_to_bits($inet) {
$inet = inet_pton($inet);
$unpacked = unpack('A16', $inet);
$unpacked = str_split($unpacked[1]);
$binaryip = '';
foreach ($unpacked as $char) {
$binaryip .= str_pad(decbin(ord($char)), 8, '0', STR_PAD_LEFT);
}
return $binaryip;
}
function bits_to_inet($bits) {
$inet = "";
for($pos=0; $pos<128; $pos+=8) {
$inet .= chr(bindec(substr($bits, $pos, 8)));
}
return inet_ntop($inet);
}
$ip = "::ffff:5.39.28.128";
$netmask = 122;
// Convert ip to binary representation
$bin = inet_to_bits($ip);
// Generate network address: Length of netmask bits from $bin, padded to the right
// with 0s for network address and 1s for broadcast
$network = str_pad(substr($bin, 0, $netmask), 128, '1', STR_PAD_RIGHT);
// Convert back to ip
print bits_to_inet($network);
Output:
::ffff:5.39.28.191
Solution is quite simple:
// Your input data
$networkstart = '5.39.28.128';
$networkmask = 122;
// First find the length of the block: IPv6 uses 128 bits for the mask
$networksize = pow(2, 128 - $networkmask);
// Reduce network size by one as we really need last IP address in the range,
// not first one of subsequent range
$networklastip = long2ip(ip2long($networkstart) + $networksize - 1);
$networklastip will have last IP address in the range.
Now this is good solution ONLY for IPv4 addresses in IPv6 address space. Otherwise you need to use IPv6 to/from 128 bit integer functions instead of ip2long/long2ip. However for use by MaxMind data code above is sufficient as I have not seen any actual IPv6 data from them yet.
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));
}
}
i have a phone number in following format +923334173333 and now i want to validate this number exactly in the given format and length with php
Fastest way would be
function validatePhoneNumber($n)
{
return strlen($n) == 13 && $n[0] == '+' && ctype_digit(substr($n, 1)) == 13;
}
validatePhoneNumber('+923334173333'); // returns true
You would need the rules for all possible types of phone numbers and their limitation in the whole world. I'm uncertain if such list even exists.
You can test the length with strlen(), and use ctype_digit() in combination with substr() to check that the substring starting after the '+' consists only of digits.