This guide shows you common practices to convert IPv4-
and IPv6-Addresses
into their binary
representation.
This guide has been written by Matthias Kaschubowski, a autodidactical software developer from germany with about 14 years of practice converting coffee to code. He works in his free time as a php evangelist on a lot of platforms ( last shown as tr0y
on php.de, a german PHP related forum and as himself as an adminstrator at the largest PHP-related facebook group ).
IP Addresses are human readable strings that identify an entity inside of TCP/IP based networks. In the past of the internet, IPv4 Addresses are the way to go to locate a server, connect clients to servers or other clients, to send mails or any other network related task. IPs in general are equal to phone numbers ( with just a different format ), but phone numbers are not limited by a specific range.
Nowadays we reached the limit of the address range of IPv4 addresses (4.294.967.296
unique clients connected at the same time), a new address format has been invented to fullfil the duties of IPv4 in the future: IPv6. Within IPv6 the internet may grow up for a while, at least 340.282.366.920.938.463.463.374.607.431.768.211.456
unique clients may connect to the biggest world wide network to exceed the new IP address range ( yes, thats a number with 39 digits ).
Full qualified IPv4 addresses are a sequence of unsigned integer, segmented into 4 groups, each segment is seperated with a .
, each group may have a value betwen 0 and 255:
127.0.0.1
The local loopback, your beloved localhost
.
Full qualified IPv6 addresses are a sequence of hexadecimals, segmented into 8 groups, each segment is seperated with a :
, each group may have a value between 0 and ffff, leading zeros may be ommited, sequenced of zeros may be ommited too:
0000:0000:0000:0000:0000:0000:0000:0001
or:
::1
Yes, that is also the local loopback.
PHP comes out of the box with support for an 32 bit
signed integer on 32 bit platforms and with support for an 64 bit
signed integer on a 64 bit plattform. To leave a statement about what this guide solves: 32 Bit integers already fails on storaging the highest possible IPv4 address. 64 Bit integers are not able to handle IPv6 addresses (which will need at least 128 bit
). PHP is able to handle large numbers bitwise with help of bcmath. In detail: with bcmath any number will be represented as an string. We will use binary strings.
Yes, you'll deal with binary strings along this guide. You'll drop them into the database aslong you want to store ip adresses that can be easily queried ( compared with other IP Addresses ) and you'll compare them with other ip addresses inside of PHP.
Do not use ip2long or long2ip. ip2long converts an human readable IPv4 address to its integer
representation. On 32-bit plattforms that may end in the PHP_MAX_INT
value if the given IP address representation is higher as the PHP_MAX_INT
. This will definitely happen aslong the first octed ( segment ) of the ip address is higher than 127
.
At first we start with the conversion of 127.0.0.1
to the binary representation:
$binaryIP = inet_pton('127.0.0.1');
Keep in mind that what we got is a binary string, var_dump()
won't help here to find out what we got, var_dump()
is not able to display binary strings and will return an dumped empty string. We simply check if we got a 32 Bit representation while counting the byte-length of the binary string along strlen
or mb_strlen
:
var_dump(strlen($binaryIP));
will result in int(4)
We continue our research with the conversion of ::1
which is the IPv6 representation of 127.0.0.1
:
$binaryIPv6 = inet_pton('::1');
We check the result in the same way to make sure that we got an 128 Bit representation ( 16 byte string length ):
var_dump(strlen($binaryIP));
will result in int(16)
The next step would be a between-like check to clearify that a given IPv4 address is between 2 IPv4 addresses:
function ip_between($ip, $ipStart, $ipEnd)
{
$start = inet_pton($ipStart);
$end = inet_pton($ipEnd);
$value = inet_pton($ip);
return $start <= $value && $end >= $value;
}
var_dump(ip_between('127.0.0.10', '127.0.0.1', '127.0.0.255'));
will result in bool(true)
Same function just with IPv6 addresses:
var_dump(ip_between('::DD', '::1', '::FFFF'));
will result in bool(true)
Handling IP conversion, no matter which type is not that hard in PHP, but you should prefer to delegate such conversions to the database level as long the conversion was only made for storaging and the database behind your application is on a level of actuality that grants the availability of inet6_aton
and inet6_ntoa
. MySQL 5.5 or higher will fullfil this requirement.
For a small test you may create the following table:
CREATE TABLE `ip_address_ranges` (
`start` VARBINARY(16),
`end` VARBINARY(16)
)
Insert 2 ranges, one for IPv4 and one for IPv6:
INSERT INTO `ip_address_ranges` ( `start`, `end` )
VALUES
( inet6_aton(`::1`), inet6_aton(`::FFFF`) ),
( inet6_aton('127.0.0.1'), inet6_aton('127.0.0.255') )
Finally a IPv6 based between query and a IPv4 based between query:
SELECT
inet6_ntoa(`start`) AS `start-IP`,
inet6_ntoa(`end`) AS `end-IP`
FROM `ip_address_ranges`
WHERE
inet6_aton('::5') BETWEEN `start` AND `end`
SELECT
inet6_ntoa(`start`) AS `start-IP`,
inet6_ntoa(`end`) AS `end-IP`
FROM `ip_address_ranges`
WHERE
inet6_aton('127.0.0.50') BETWEEN `start` AND `end`
Both selects will result 1 row and show you the human readable representation of the range that matches the query.
A small hint: MySQL has also inet_aton
- and inet_ntoa
-functions, prefer inet6_*
-functions due to compability for IPv6 and IPv4. the inet_*
-functions are implemented to handle IPv4 addresses only.
Does it help ? - Feel free to comment, no matter if you may donate blood or honor.
Inspirations for new guides or requests may be send to [email protected]
.
Dropped to Github at 2014/08/23.
inet6_ntoa()
andinet6_aton()
require MySQL 5.6!